Hi, I’m Stephan, the Visual C++ Libraries Developer working on TR1.
Recently, I gave 3 presentations within Microsoft about the most novel components of TR1: shared_ptr, regex, and the additions to <functional> (including mem_fn(), bind(), and tr1::function). These presentations explained where to use, how to use, and how to avoid misusing these components, with plenty of examples.
Attached at the end of this post is a ZIP of the slide decks for each of the presentations, in PowerPoint 2007 format.
Each slide deck mostly stands alone, although the “shared_ptr To Noncopyable” and “shared_ptr To Polymorphic” examples require some context and additional explanation:
The “shared_ptr To Noncopyable” example demonstrates how to create a container of noncopyable resource managers. A resource is something that must be acquired from the system and then released afterwards. The most common resource is memory (acquired in C++ with new, and released with delete), but there are many non-memory resources, such as files, sockets, textures, fonts, and so forth. A resource manager is an object that encapsulates a resource, controlling its lifetime and permitting access to it. Resource managers acquire resources in their constructors, and release resources in their destructors (this is the somewhat-mysteriously-named RAII principle: Resource Acquisition Is Initialization, implying RRID: Resource Release Is Destruction). std::vector is an example of a resource manager for memory, while std::ifstream is an example of a resource manager for files. Memory resource managers are usually copyable (and assignable); chunks of memory can easily be copied and assigned. But non-memory resource managers are usually noncopyable (and nonassignable); either their underlying resources can’t be copied without additional information (e.g. copying a file requires a new name), or copying them is a relatively expensive operation (e.g. textures consume precious video card memory, etc.). So, it’s best to prohibit copy construction and copy assignment of non-memory resource managers. This requires programmers to explicitly copy the underlying resources when that’s desired.
The problem here is that STL (and TR1) containers require ordinary value types. An ordinary value type is “something that behaves like an int”; ints are copyable and assignable. vectors copy their contained elements when undergoing reallocation, and so forth. You can’t have a vector<ifstream> because ifstream is noncopyable. But sometimes, that’s exactly what you want.
In this example (admittedly somewhat contrived), I’m writing a program to interleave text files. The user will have an arbitrary number of text files, and will provide the names of the files to the program. Then, the program will have to loop through the files in order, printing out the first line of each file, then the second line, and so forth. When a text file runs out of lines (they can have different numbers of lines), the program has to start skipping that file. When all the lines from all the files have been printed, the program has to stop.
Now, I could write this by reading each file into a vector<string> (storing each line), and building up a vector<vector<string> > (storing each file), and then looping through that. But that would require reading everything into memory. Suppose I don’t want to do that.
Well, if I could use a vector<ifstream>, I could repeatedly loop through the vector, reading one line from each file in turn. When a file ran out of lines, I could remove it from the vector. But ifstream is noncopyable.
To solve this, I can use shared_ptr. shared_ptr can be used as a handle to a noncopyable object; the shared_ptr itself is copyable and assignable.
Slide 1/3 of the example shows the contents of 3 text files.
Slide 2/3 shows the code. To fit the code onto a single slide, I use a queue<shared_ptr<ifstream> >. I begin by reading filenames from cin (with the for-loop). From each filename, I new up an ifstream, held by shared_ptr, and push it into the queue. At the end of this for-loop, the queue contains shared_ptrs to the files mentioned by the user in order.
Then, I loop through the queue (with the while-loop) as long as it contains files to process. I attempt to read a line from the file at the front of the queue (this is the next file to process). If that’s successful (the file still contains lines), I print it out. I want to keep working on that file, so I push() a copy of the shared_ptr to this file (q.front()), to the back of the queue. Then I pop() the front of the queue. (At this point, particularly attentive readers should ask whether q.push(q.front()) raises the specter of iterator invalidation; the answer is that it does not. I would have avoided this in the interests of clarity, but it would have taken more lines.) If I wasn’t able to read a line from the file, then it’s empty, so I simply want to pop it from the front of the queue. Then I continue with the loop.
Slide 3/3 shows the filenames being entered, and the program’s output.
The “shared_ptr To Polymorphic” example demonstrates how to create a container of polymorphic objects (i.e. classes with virtual functions). Polymorphic objects should always be noncopyable, because of the danger of slicing. Inheritance means substitutability; a Derived object has to be usable wherever a Base object would be usable. However, copying presents a problem. If Base is copyable, then you can copy from a Base to a Base. But substitutability then implies that you can copy from a Derived (which is a Base) to a Base. This “slices” off any Derived behavior and state, which is almost always undesirable. Therefore, copy construction and copy assignment of polymorphic base classes should be prohibited. (You should provide a virtual clone() function if you need to copy polymorphic objects safely.)
If Base is noncopyable, you can’t have a vector<Base>. You could try to use a vector<Base *>, newing up Derived objects and storing pointers to them, but that would be highly likely to leak, especially in the presence of exceptions. As with resource managers, shared_ptr allows us to manipulate noncopyable objects with copyable handles. vector<shared_ptr<Base> > is a powerful construct that can be used in many situations.
In this example (even more contrived; you can mentally substitute your favorite Base and Derived classes), I have a polymorphic base class Animal. Therefore, it has a virtual destructor, no copy constructor, and no copy assignment operator. It stores a string m_name (the name of the Animal), and has an explicit constructor taking that name. Animal uses the Non-Virtual Inheritance idiom, so it has a public, non-virtual member function named noise(), which returns “<name> says <something>”. The “something” is customizable by derived classes, through the private pure virtual member function noise_impl(). (Yes, private virtual member functions can be overridden; the difference is that derived classes cannot call the base implementation, whereas they could if the base implementation were protected. As noise_impl() is pure, there is no base implementation, so the difference is theoretical – but I prefer to lock down access control unless I need looser control.)
Slide 2/4 shows three derived classes, Cat, Dog, and Pig, identical in structure except for what their noise_impl()s return.
Slide 3/4 constructs a vector<shared_ptr<Animal> >, which is then filled with a Cat, Dog, and Pig. Then I print out the noises that each Animal makes. I use the STL algorithm transform() on the vector, printing strings to cout separated by newlines. mem_fn(&Animal::noise) uses tr1::mem_fn() to adapt a pointer-to-member-function (&Animal::noise) to a function-object (what transform() wants).
Slide 4/4 shows the output, demonstrating that the virtual function calls worked as usual.
The point of these two examples is that shared_ptr is useful for much more than sharing and exception safety. shared_ptr allows you to use the STL in more situations, by wrapping noncopyable objects in copyable clothing.
If you have any questions, I’ll be happy to answer them in the Comments!
Stephan T. Lavavej
0 comments