Exercises






Exercises

Exercise 1

For each of the following errors, write a simple test case containing the error, and try to compile it. In the error messages, look for the key words that relate to the error in the code.

  1. Attempting to construct a shared_ptr object with a pointer type that is not convertible to the pointer type held by the shared_ptr

  2. Attempting to construct a shared_ptr object with another shared_ptr object that holds a pointer type that is not convertible to the pointer type held by the object being constructed

  3. Attempting to construct a shared_ptr object with a pointer and a deleter object whose function call operator can't be called with the original pointer

  4. Attempting to convert a shared_ptr<int> to a shared_ptr<double> with static_pointer_cast

Exercise 2

Write a program that demonstrates the use of shared_ptr accessors. Begin by defining a simple struct that has one public data member and a default constructor that initializes that data member. Create a shared_ptr object that points to an object of this type. Display the value of the stored object's data member three times: once using shared_ptr::get(), once using shared_ptr::operator*, and once using shared_ptr::operator->.

Exercise 3

How many different ways can you think of to determine whether a shared_ptr object holds a non-null pointer? Write a program to verify that they all work correctly.

Exercise 4

Write a program consisting of a type definition, a function template, and a main function. The type definition should be a class whose constructor and destructor write logging messages to cout. (Use the instrumented class from sputil.h if you like). The function template should take an argument of an arbitrary type by value. Its body should simply write a logging message to cout, showing that it was called. In main, create an object of your logging type on the heap, and create a shared_ptr object to manage it. Call the function template with this shared_ptr object. Add logging messages to main so that you can see when the heap object is destroyed relative to the call to the function template. Do the same thing using an auto_ptr object to manage the heap object. Explain the output.

Exercise 5

Write a program that allocates an array of objects on the heap and manages that array with a shared_ptr object. Check that the array is properly deleted when it is no longer in use.

Exercise 6

I said in Section 2.4.2 that you shouldn't construct two shared_ptr objects from the same pointer. The danger is that both shared_ptr objects or their progeny will eventually try to delete the resource, and that usually leads to trouble. In fact, you can do this if you're careful. It's not particularly useful, but write a program that constructs two shared_ptr<instrumented> objects from the same pointer and deletes the resource only once.

Exercise 7

Write a program consisting of two functionsthe main function and a function named write. The write function should take an argument of type shared_ptr<FILE> and should write some text to the C-style stream identified by the FILE* held in the shared_ptr object. (Use the member function get() to get the pointer.) The main function should create two shared_ptr<FILE> objects. One should hold the pointer returned by a call to fopen and should properly close the stream when the last shared_ptr object managing that file is destroyed. The other should hold the pointer stdout and should not close the stream. The main function should call write with each of shared_ptr objects.

Exercise 8

Assume that you have the following objects:

// EMPTY shared_ptr OBJECTS
shared_ptr<int> sp0;
shared_ptr<int> sp1 (sp0);
shared_ptr<int> sp2;

// shared_ptr OBJECTS HOLDING NULL POINTERS
shared_ptr<int> sp3 ((int *)0);
shared_ptr<int> sp4 (sp3);
shared_ptr<int> sp5 ((int *)0);

// shared_ptr OBJECTS HOLDING NON-NULL POINTERS
shared_ptr<int> sp6 (new int (3));
shared_ptr<int> sp7 (sp6);
shared_ptr<int> sp8 (new int (3));

  1. What should the result of the following comparisons be?

    1. sp0 == sp1

    2. sp0 == sp2

    3. sp0 == sp3

    4. sp3 == sp4

    5. sp3 == sp5

    6. sp0 == sp6

    7. sp6 == sp7

    8. sp6 == sp8

  2. Write a program to verify your answers.

  3. Consider the following function:

    template <class Ty1, class Ty2>
    bool equiv(shared_ptr<Ty1> left,
      shared_ptr<Ty2> right)
      {
      return !(left < right) && !(right < left);
      }
    

    Logically, it makes sense to say that left and right are equivalent if left is not less than right and right is not less than left. In fact, since operator< for shared_ptr types defines a strict weak ordering, it can be shown that equiv in turn is an equivalence relation, as its name suggests. In Section 2.4.8, we saw that this equivalence relation is required to be true only for objects that are empty or share ownership of the same resource. That's different from the rule for operator==. What should the result of the following function calls be?

    1. equiv(sp0, sp1)

    2. equiv(sp0, sp2)

    3. equiv(sp0, sp3)

    4. equiv(sp3, sp4)

    5. equiv(sp3, sp5)

    6. equiv(sp0, sp6)

    7. equiv(sp6, sp7)

    8. equiv(sp6, sp8)

  4. Write a program to verify your answers.

Exercise 9

  1. Use the function template equiv given in the previous exercise and the following data types and data objects:

    // TYPES
    struct base1 {};
    struct base2 {};
    struct derived : base1, base2 {};
    
    // OBJECTS
    shared_ptr<derived> sp0(new derived);
    shared_ptr<base1> sp1(sp0);
    shared_ptr<base2> sp2(sp0);
    shared_ptr<derived> sp3(new derived);
    

    What should the result of the following function calls be?

    1. equiv(sp0, sp1)

    2. equiv(sp0, sp2)

    3. equiv(sp1, sp2)

    4. equiv(sp3, sp1)

    5. equiv(sp3, sp2)

    Write a program to verify your answers.

  2. What should happen if you use == instead of equiv for the comparisons? Write a program to verify your answers.

Exercise 10

Write a program consisting of a function named do_search and a main function. The do_search function should take one argument of type shared_ptr<int> and one argument of type const std::vector<shared_ptr<int>>&. Assuming that the contents of the vector object are sorted, it should search for the shared_ptr<int> object in the vector object twice, first using the std::find algorithm and then using the std::binary_search algorithm, and report the results of both searches.

The main function should create several objects of type shared_ptr<int>, with each holding a pointer to an int object allocated on the heap. These int objects should all have different values. It should also create a shared_ptr<int> object that holds a null pointer and an empty shared_ptr<int> object. It should insert each of those shared_ptr<int> objects into an std:: vector<shared_ptr<int>> and use std::sort to sort the contents of the vector. Then it should do the following:

  1. Iterate through the container, showing the value, in order, of each of the int objects that the container elements control. Be careful not to dereference any null pointers.

  2. Call do_search, passing the empty shared_ptr object and the vector object.

  3. Call do_search, passing the shared_ptr object that holds a null pointer and the vector object.

  4. Call do_search, passing one of the shared_ptr objects that holds a non-null pointer and the vector object.

  5. Create another empty shared_ptr<int> object that is not a copy of the original one, and call do_search with the new object and the vector object.

  6. Create another shared_ptr<int> object that holds a null pointer and is not a copy of the original one, and call do_search with the new object and the vector object.

  7. Create another shared_ptr<int> object that holds a pointer to an int object allocated on the heap and is not a copy of any of the original ones. The value of the int object should be the same as the value of one of the original ones. Call do_search with the new object and the vector object.

Explain the results.

Exercise 11

A double-linked list consists of nodes that hold a data item, a pointer to the next node in the list, and a pointer to the previous node in the list. For purposes of this exercise, the last node in the list stores a null pointer as its pointer to its next node, and the first node in the list stores a null pointer as its pointer to its previous node. You access the contents of the list through a pointer that points to the first node in the list.

  1. Write a class template template <class Data> node that holds an object of type Data named data and two pointers named next and prev that point to other objects of type node<Data>. Write another class template, template <class Data> list, that holds a pointer named head that points to an object of type node<Data>. The constructor for this template should initialize head to 0. Add a member function void list::insert(const Data& val) that inserts a new node<Data> object holding the value val at the head of the linked list. Add a member function bool list::remove(const Data& val) that removes all nodes whose data member is equal to val from the list. Make sure that list's destructor deletes any nodes remaining in the list.

  2. Rewrite the previous example, using objects of type shared_ptr<node <Data>> instead of pointers in the template node and in place of the pointer named head in the template list. Remove the destructor from list; it shouldn't be needed, because you're now using shared_ptr objects instead of pointers. Why doesn't this version of list destroy the remaining nodes when the list object goes out of scope?

  3. Fix the memory leak in the previous example.

Exercise 12

Write three functions that each take a weak_ptr object and return a shared_ptr object. If the weak_ptr object has expired, the shared_ptr object returned by each function should be empty; otherwise, it should own the resource that the weak_ptr argument points to. Each function should, of course, use a different technique to make this determination. Write a program to verify that all three functions work correctly.

Exercise 13

In Section 2.8, we talked about implicit conversions between shared_ptr types and the three function templates for explicit conversions. Consider this class hierarchy:

class A { virtual void f () {} };
class B {};
class G : public A {};
class H : public B {};
class I : public virtual A {};
class J : public virtual B {};
class Z : public G, public H,
  public I, public J {};

  1. Beginning with an object shared_ptr<Z> spz(new Z), which of the following assignments is valid as written, which can be made valid with an explicit conversion, and which ones are simply not allowed?

    1. shared_ptr<G> spg = spz;

    2. shared_ptr<Z> t0 = spg;

    3. shared_ptr<A> spa0 = spg;

    4. shared_ptr<G> t1 = spa0;

    5. shared_ptr<H> sph = spz;

    6. shared_ptr<const B> spb0 = sph;

    7. shared_ptr<H> t2 = spb0;

    8. shared_ptr<I> spi = spz;

    9. shared_ptr<A> spa1 = spi;

    10. shared_ptr<I> t3 = spa1;

    11. shared_ptr<J> spj = spz;

    12. shared_ptr<B> spb1 = spj;

    13. shared_ptr<J> t4 = spb1;

    14. shared_ptr<G> spgx = spb1;

    15. shared_ptr<H> sphx = spa1;

  2. Write and compile some code to verify your answers.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows