Reading Notes Objective c ++ Item 45 use the member function template to accept "all compatible types" and "Every tiveitem"
Smart pointers act like pointers, but do not provide the Add function. For example, Item 13 explains how to use the standard auto_ptr and tr1: shared_ptr pointers to automatically delete resources on the stack at the right time. The iterator In the STL container is basically a smart pointer: Of course, you cannot use "++" to move the built-in pointer pointing to a node in the linked list to the next node, but list :: iterator can do this.
1. Problem Analysis-how to implement implicit conversion of smart pointers
One thing true pointers can do is support implicit conversion. A pointer to a derived class can be implicitly converted to a base class pointer. A pointer to a non-const can be implicitly converted to a pointer to a const object. For example, consider the conversion that can occur in a three-tier inheritance system:
1 class Top { ... };2 class Middle: public Top { ... };3 class Bottom: public Middle { ... };4 Top *pt1 = new Middle; // convert Middle* ⇒ Top*5 6 Top *pt2 = new Bottom; // convert Bottom* ⇒ Top*7 8 const Top *pct2 = pt1; // convert Top* ⇒ const Top*
It is very subtle to imitate this conversion in user-defined smart pointers. We want to compile the following code:
1 template<typename T> 2 class SmartPtr { 3 public: // smart pointers are typically 4 explicit SmartPtr(T *realPtr); // initialized by built-in pointers 5 ... 6 }; 7 SmartPtr<Top> pt1 = // convert SmartPtr<Middle> ⇒ 8 SmartPtr<Middle>(new Middle); // SmartPtr<Top> 9 SmartPtr<Top> pt2 = // convert SmartPtr<Bottom> ⇒10 SmartPtr<Bottom>(new Bottom); // SmartPtr<Top>11 SmartPtr<const Top> pct2 = pt1; // convert SmartPtr<Top> ⇒12 // SmartPtr<const Top>
There is no inherent relationship between different instances of the same template. Therefore, the compiler regards SmartPtr <Middle> and SmartPtr <Top> as completely different classes, the relationship between them is no closer than that of vector <float> and Widget. To implement conversions between SmartPtr classes, we must display the implementation.
In the preceding smart pointer sample code, each statement creates a new smart pointer object, so now we focus on how to implement a smart pointer constructor that shows behavior as we wish. The key point is that there is no way to implement all the constructors we need. In the above inheritance system, we can use a SmartPtr <Middle> or a SmartPtr <Bottom> to construct a SmartPtr <Top>. However, if this inheritance system is extended in the future, smartPtr <Top> objects must be constructed from other smart pointer types. For example, if we add the following class:
1 class BelowBottom: public Bottom { ... };
We will need to support using the SmartPtr <BelowBottom> object to create the SmartPtr <Top> object. We certainly do not want to modify the SmartPtr template to implement it.
2. Use the member function template-generalized copy constructor for implicit conversion
In principle, there is no limit on the number of constructors we need. Since the template can be instantiated into a function with no limit, it seems that we do not need a SmartPtr constructor. What we need is a constructor template. This template isMember function Template(Member function templates) (also called member templates) is an example-that is, a template that generates member functions for the class:
1 template<typename T>2 class SmartPtr {3 public:4 template<typename U> // member template5 SmartPtr(const SmartPtr<U>& other); // for a ”generalized6 7 ... // copy constructor”8 9 };
This means that for each type of T and U, A SmartPtr <T> can be created using SmartPtr <U>, because SmartPtr <T> has a constructor with SmartPtr <U> as the parameter. Constructor like this: create another object with an object. Two objects come from the same template but they are of different types (for example, use SmartPtr <U> to create SmartPtr <T>), which is usually calledGeneralized copy constructor(Generalized copy constructors ).
2.1 explicit it is not required for implicit conversion
The above generalized copy constructor is not declared as explicit. This is carefully considered. The type conversion between built-in pointer types (for example, from a derived class to a base class pointer) is implicit and does not require cast. Therefore, intelligent pointer imitation is reasonable. Explicit it is omitted in the templated constructor to achieve this.
2.2 remove non-conforming template instantiation Functions
The generalized copy constructor implemented by SmartPtr provides more things than we want. We want to use SmartPtr <Bottom> to create SmartPtr <Top>, but we do not want to use SmartPtr <Top> to create SmartPtr <Bottom> because it violates the meaning of public inheritance (Item 32 ). We also do not want to use SmartPtr <double> to create SmartPtr <int> because there is no implicit conversion from double * to int. Therefore, we must remove the member function set generated by the member template.
Assuming that SmartPtr complies with the auto_ptr and tr1: shared_ptr designs, it also provides a get member function to return a copy of the built-in type pointer contained in the smart pointer object (Item 15 ), we can use the implementation of the constructor template to limit some conversions:
1 template<typename T> 2 class SmartPtr { 3 public: 4 template<typename U> 5 SmartPtr(const SmartPtr<U>& other) // initialize this held ptr 6 : heldPtr(other.get()) { ... } // with other’s held ptr 7 T* get() const { return heldPtr; } 8 ... 9 private: // built-in pointer held10 T *heldPtr; // by the SmartPtr11 }
In the member initialization list, we use the pointer of the type U * In SmartPtr <U> to initialize the data member of the type T * In SmartPtr <T>. This can be compiled only when implicit conversion can be performed from the U * pointer to the T * pointer, which is exactly what we need. The actual result is that currently SmartPtr <T> has a generalized copy constructor, which can be compiled only when the passed parameter is of a compatible type.
3. Support for value assignment by member function templates
The use of member function templates is not limited to constructors. Another common role is the support for value assignment. For example, shared_ptr (Item 13) of tr1 can be constructed using all compatible built-in pointers. You can use tr1: shared_ptr, auto_ptr, and tr1: weak_ptr (Item 54) for construction, the assignment is also used, but the tr1: weak_ptr exception. The following is the implementation of tr1: shared_ptr extracted from the tr1 description. We can see that it tends to use class instead of typename when declaring template parameters. (As described in Item 42, they have the same meaning in this context .)
1 template<class T> class shared_ptr { 2 public: 3 4 template<class Y> // construct from 5 6 explicit shared_ptr(Y * p); // any compatible 7 8 template<class Y> // built-in pointer, 9 10 11 shared_ptr(shared_ptr<Y> const& r); // shared_ptr,12 template<class Y> // weak_ptr, or13 14 explicit shared_ptr(weak_ptr<Y> const& r); // auto_ptr15 16 template<class Y> 17 18 explicit shared_ptr(auto_ptr<Y>& r); 19 20 template<class Y> // assign from21 shared_ptr& operator=(shared_ptr<Y> const& r); // any compatible22 template<class Y> // shared_ptr or23 shared_ptr& operator=(auto_ptr<Y>& r); // auto_ptr24 ...25 };
All of these constructor functions are explicit except generalized copy constructor. This means that implicit conversion from one type of shared_ptr to another type of shared_ptr is allowed, however, implicit conversions between built-in and other smart pointer types to shared_ptr are prohibited. (The displayed conversion is acceptable (for example, by using cast )). It is also interesting that the auto_ptr passed to the tr1: shared_ptr constructor and the value assignment operator is not declared as const, but tr1: shared_ptr and tr1 :: the transfer of weak_ptr is declared as const. This is because the auto_ptr has been modified when it is copied (Item 13 ).
4. The member function template generates the default copy constructor.
Member function templates are good things, but they do not have the basic rules to modify the language. Item 5 explains that two of the four member functions automatically generated by the compiler are the copy constructor and the copy assignment operator. Tr1: shared_ptr declares a generalized copy constructor. It is clear that if the type T and the Type Y are the same, the generalized copy constructor is instantiated into a "normal" copy constructor. Will the compiler generate a copy constructor for tr1: shared_ptr? Or will the compiler instantiate the generalized copy constructor when constructing another tr1: shared_ptr with the same type of tr1: shared_ptr?
As I said, the member template does not have rules to modify the language. "If you need a copy constructor and you do not declare it yourself, the compiler will generate one for you." This rule is also one of them. Declaring a generalized copy constructor (a member template) in a class does not prevent the compiler from generating their own copy constructor (non-template ), so if you want to control all aspects of the copy constructor, you must declare both a generalized copy constructor and a common constructor. The same applies to assignments. The following is the definition of tr1: shared_ptr:
1 template<class T> class shared_ptr { 2 public: 3 shared_ptr(shared_ptr const& r); // copy constructor 4 5 template<class Y> // generalized 6 7 8 9 shared_ptr(shared_ptr<Y> const& r); // copy constructor10 11 shared_ptr& operator=(shared_ptr const& r); // copy assignment12 13 template<class Y> // generalized14 15 16 shared_ptr& operator=(shared_ptr<Y> const& r); // copy assignment17 ...18 };
5. Summary
- Use the member function template to generate functions that accept all compatible types.
- If you declare a member template for the generalized copy constructor and the generalized value assignment operator, you also need to declare the common copy constructor and the copy assignment operator.