More Books
Effective C++ 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Table of Contents
Copyright
Praise for Effective C++, Third Edition
Addison-Wesley Professional Computing Series
Preface
Acknowledgments
Introduction
Terminology
Chapter 1. Accustoming Yourself to C++
Item 1: View C++ as a federation of languages
Item 2: Prefer consts, enums, and inlines to #defines
Item 3: Use const whenever possible
Item 4: Make sure that objects are initialized before they're used
Chapter 2. Constructors, Destructors, and Assignment Operators
Item 5: Know what functions C++ silently writes and calls
Item 6: Explicitly disallow the use of compiler-generated functions you do not want
Item 7: Declare destructors virtual in polymorphic base classes
Item 8: Prevent exceptions from leaving destructors
Item 9: Never call virtual functions during construction or destruction
Item 10: Have assignment operators return a reference to *this
Item 11: Handle assignment to self in operator=
Item 12: Copy all parts of an object
Chapter 3. Resource Management
Item 13: Use objects to manage resources.
Item 14: Think carefully about copying behavior in resource-managing classes.
Item 15: Provide access to raw resources in resource-managing classes.
Item 16: Use the same form in corresponding uses of new and delete.
Item 17: Store newed objects in smart pointers in standalone statements.
Chapter 4. Designs and Declarations
Item 18: Make interfaces easy to use correctly and hard to use incorrectly
Item 19: Treat class design as type design
Item 20: Prefer pass-by-reference-to-const to pass-by-value
Item 21: Don't try to return a reference when you must return an object
Item 22: Declare data members private
Item 23: Prefer non-member non-friend functions to member functions
Item 24: Declare non-member functions when type conversions should apply to all parameters
Item 25: Consider support for a non-throwing swap
Chapter 5. Implementations
Item 26: Postpone variable definitions as long as possible.
Item 27: Minimize casting.
Item 28: Avoid returning "handles" to object internals.
Item29: Strive for exception-safe code.
Item 30: Understand the ins and outs of inlining.
Item31: Minimize compilation dependencies between files.
Chapter 6. Inheritance and Object-Oriented Design
Item 32: Make sure public inheritance models "is-a."
Item 33: Avoid hiding inherited names
Item 34: Differentiate between inheritance of interface and inheritance of implementation
Item 35: Consider alternatives to virtual functions
Item 36: Never redefine an inherited non-virtual function
Item 37: Never redefine a function's inherited default parameter value
Item 38: Model "has-a" or "is-implemented-in-terms-of" through composition
Item 39: Use private inheritance judiciously
Item 40: Use multiple inheritance judiciously
Chapter 7. Templates and Generic Programming
Item 41: Understand implicit interfaces and compile-time polymorphism
Item 42: Understand the two meanings of typename
Item 43: Know how to access names in templatized base classes
Item 44: Factor parameter-independent code out of templates
Item 45: Use member function templates to accept "all compatible types."
Item 46: Define non-member functions inside templates when type conversions are desired
Item 47: Use traits classes for information about types
Item 48: Be aware of template metaprogramming
Chapter 8. Customizing new and delete
Item 49: Understand the behavior of the new-handler
Item 50: Understand when it makes sense to replace new and delete
Item 51: Adhere to convention when writing new and delete
Item 52: Write placement delete if you write placement new
Chapter 9. Miscellany
Item 53: Pay attention to compiler warnings.
Item 54: Familiarize yourself with the standard library, including TR1
Item.55: Familiarize yourself with Boost.
Appendix A. Beyond Effective C++
Appendix B. Item Mappings Between Second and Third Editions
Index
index_SYMBOL
index_A
index_B
index_C
index_D
index_E
index_F
index_G
index_H
index_I
index_J
index_K
index_L
index_M
index_N
index_O
index_P
index_R
index_S
index_T
index_U
index_V
index_W
index_X
index_Z

Item 45: Use member function templates to accept "all compatible types."

Smart pointers are objects that act much like pointers but add functionality pointers don't provide. For example, Item 13 explains how the standard auto_ptr and tr1::shared_ptr can be used to automatically delete heap-based resources at the right time. Iterators into STL containers are almost always smart pointers; certainly you couldn't expect to move a built-in pointer from one node in a linked list to the next by using "++," yet that works for list::iterators.

One of the things that real pointers do well is support implicit conversions. Derived class pointers implicitly convert into base class pointers, pointers to non-const objects convert into pointers to const objects, etc. For example, consider some conversions that can occur in a three-level hierarchy:


class Top { ... };

class Middle: public Top { ... };

class Bottom: public Middle { ... };

Top *pt1 = new Middle;                   // convert Middle* Top*

Top *pt2 = new Bottom;                   // convert Bottom* Top*

const Top *pct2 = pt1;                   // convert Top*  const Top*


Emulating such conversions in user-defined smart pointer classes is tricky. We'd need the following code to compile:


template<typename T>

class SmartPtr {

public:                             // smart pointers are typically

  explicit SmartPtr(T *realPtr);    // initialized by built-in pointers

  ...

};



SmartPtr<Top> pt1 =                 // convert SmartPtr<Middle> 

  SmartPtr<Middle>(new Middle);     //   SmartPtr<Top>



SmartPtr<Top> pt2 =                 // convert SmartPtr<Bottom> 

  SmartPtr<Bottom>(new Bottom);     //   SmartPtr<Top>



SmartPtr<const Top> pct2 = pt1;     // convert SmartPtr<Top> 

                                    //  SmartPtr<const Top>


There is no inherent relationship among different instantiations of the same template, so compilers view SmartPtr<Middle> and SmartPtr<Top> as completely different classes, no more closely related than, say, vector<float> and Widget. To get the conversions among SmartPtr classes that we want, we have to program them explicitly.

In the smart pointer sample code above, each statement creates a new smart pointer object, so for now we'll focus on how to write smart pointer constructors that behave the way we want. A key observation is that there is no way to write out all the constructors we need. In the hierarchy above, we can construct a SmartPtr<Top> from a SmartPtr<Middle> or a SmartPtr<Bottom>, but if the hierarchy is extended in the future, SmartPtr<Top> objects will have to be constructible from other smart pointer types. For example, if we later add


class BelowBottom: public Bottom { ... };


we'll need to support the creation of SmartPtr<Top> objects from SmartPtr<BelowBottom> objects, and we certainly won't want to have to modify the SmartPtr template to do it.

In principle, the number of constructors we need is unlimited. Since a template can be instantiated to generate an unlimited number of functions, it seems that we don't need a constructor function for SmartPtr, we need a constructor template. Such templates are examples of member function templates (often just known as member templates) — templates that generate member functions of a class:


template<typename T>

class SmartPtr {

public:

  template<typename U>                       // member template

  SmartPtr(const SmartPtr<U>& other);        // for a "generalized

  ...                                        // copy constructor"

};


This says that for every type T and every type U, a SmartPtr<T> can be created from a SmartPtr<U>, because SmartPtr<T> has a constructor that takes a SmartPtr<U> parameter. Constructors like this — ones that create one object from another object whose type is a different instantiation of the same template (e.g., create a SmartPtr<T> from a SmartPtr<U>) — are sometimes known as generalized copy constructors.

The generalized copy constructor above is not declared explicit. That's deliberate. Type conversions among built-in pointer types (e.g., from derived to base class pointers) are implicit and require no cast, so it's reasonable for smart pointers to emulate that behavior. Omitting explicit on the templatized constructor does just that.

As declared, the generalized copy constructor for SmartPtr offers more than we want. Yes, we want to be able to create a SmartPtr<Top> from a SmartPtr<Bottom>, but we don't want to be able to create a SmartPtr<Bottom> from a SmartPtr<Top>, as that's contrary to the meaning of public inheritance (see Item 32). We also don't want to be able to create a SmartPtr<int> from a SmartPtr<double>, because there is no corresponding implicit conversion from int* to double*. Somehow, we have to cull the herd of member functions that this member template will generate.

Assuming that SmartPtr follows the lead of auto_ptr and TR1::shared_ptr by offering a get member function that returns a copy of the built-in pointer held by the smart pointer object (see Item 15), we can use the implementation of the constructor template to restrict the conversions to those we want:


template<typename T>

class SmartPtr {

public:

  template<typename U>

  SmartPtr(const SmartPtr<U>& other)         // initialize this held ptr

  : heldPtr(other.get()) { ... }             // with other's held ptr



  T* get() const { return heldPtr; }

  ...



private:                                     // built-in pointer held

  T *heldPtr;                                // by the SmartPtr

};


We use the member initialization list to initialize SmartPtr<T>'s data member of type T* with the pointer of type U* held by the SmartPtr<U>. This will compile only if there is an implicit conversion from a U* pointer to a T* pointer, and that's precisely what we want. The net effect is that SmartPtr<T> now has a generalized copy constructor that will compile only if passed a parameter of a compatible type.

The utility of member function templates isn't limited to constructors. Another common role for them is in support for assignment. For example, TR1's shared_ptr (again, see Item 13) supports construction from all compatible built-in pointers, tr1::shared_ptrs, auto_ptrs, and tr1::weak_ptrs (see Item 54), as well as assignment from all of those except tr1::weak_ptrs. Here's an excerpt from TR1's specification for TR1::shared_ptr, including its penchant for using class instead of typename

when declaring template parameters. (As Item 42 explains, they mean exactly the same thing in this context.)


template<class T> class shared_ptr {

public:

  template<class Y>                                     // construct from

    explicit shared_ptr(Y * p);                         // any compatible

  template<class Y>                                     // built-in pointer,

    shared_ptr(shared_ptr<Y> const& r);                 // shared_ptr,

  template<class Y>                                     // weak_ptr, or

    explicit shared_ptr(weak_ptr<Y> const& r);          // auto_ptr

  template<class Y>

    explicit shared_ptr(auto_ptr<Y>& r);

  template<class Y>                                     // assign from

    shared_ptr& operator=(shared_ptr<Y> const& r);      // any compatible

  template<class Y>                                     // shared_ptr or

    shared_ptr& operator=(auto_ptr<Y>& r);              // auto_ptr

  ...

};


All these constructors are explicit, except the generalized copy constructor. That means that implicit conversion from one type of shared_ptr to another is allowed, but implicit conversion from a built-in pointer or other smart pointer type is not permitted. (Explicit conversion — e.g., via a cast — is okay.) Also interesting is how the auto_ptrs passed to TR1::shared_ptr constructors and assignment operators aren't declared const, in contrast to how the TR1::shared_ptrs and tr1::weak_ptrs are passed. That's a consequence of the fact that auto_ptrs stand alone in being modified when they're copied (see Item 13).

Member function templates are wonderful things, but they don't alter the basic rules of the language. Item 5 explains that two of the four member functions that compilers may generate are the copy constructor and the copy assignment operator. tr1::shared_ptr declares a generalized copy constructor, and it's clear that when the types T and Y are the same, the generalized copy constructor could be instantiated to create the "normal" copy constructor. So will compilers generate a copy constructor for TR1::shared_ptr, or will they instantiate the generalized copy constructor template when one TR1::shared_ptr object is constructed from another tr1::shared_ptr object of the same type?

As I said, member templates don't change the rules of the language, and the rules state that if a copy constructor is needed and you don't declare one, one will be generated for you automatically. Declaring a generalized copy constructor (a member template) in a class doesn't keep compilers from generating their own copy constructor (a non-template), so if you want to control all aspects of copy construction, you must declare both a generalized copy constructor as well as the "normal" copy constructor. The same applies to assignment. Here's an excerpt from tr1::shared_ptr's definition that exemplifies this:


template<class T> class shared_ptr {

public:

  shared_ptr(shared_ptr const& r);                 // copy constructor



  template<class Y>                                // generalized

    shared_ptr(shared_ptr<Y> const& r);            // copy constructor



  shared_ptr& operator=(shared_ptr const& r);      // copy assignment



  template<class Y>                                // generalized

    shared_ptr& operator=(shared_ptr<Y> const& r); // copy assignment

  ...

};


Things to Remember

  • Use member function templates to generate functions that accept all compatible types.

  • If you declare member templates for generalized copy construction or generalized assignment, you'll still need to declare the normal copy constructor and copy assignment operator, too.