Effective C++ Item 45: Templates and Generic Programming-Use member function templates to accept all compatible types

One, add a member function template

Take an example, when to design a member function template

  • The first step: We know that one of the characteristics of pointers is: support for implicit conversion . For example, "pointers to non-const objects can be converted to const objects", "derived class pointers can be implicitly converted to base class pointers" and so on. code show as below:
class Top {};

class Middle :public Top {};

class Bottom :public Middle {};


Top* pt1 = new Middle; //将Miffle*转换为Top*

Top* pt2 = new Bottom; //将Bottom*转换为Top*

const Top* pct2 = pt1; //将Top*转换为const Top*
  • Step 2: Assume that we now design a template to imitate the smart pointer class, and hope that the smart pointer can perform type conversion like a normal pointer. E.g:
class Top {};

class Middle :public Top {};

class Bottom :public Middle {};


//自己设计的智能指针类

template<typename T>

class SmartPtr

{

public:

    explicit SmartPtr(T* realPtr);

};


int main()

{

    //下面是我们希望能完成的,但是还没有实现

    SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);
    
    SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);

    SmartPtr<const Top> pct2 = pt1;


    return 0;

}
  • Step 3: According to the above requirements, we hope that our smart pointer class can perform type conversion like ordinary pointers, then we can design a copy constructor or copy assignment operator for SmartPtr, then the above functions can be realized.
    • An inefficient approach is to define a copy constructor and copy assignment operator for each Top derived class in the SmartPtr template. But this approach is very inefficient, because designing the corresponding copy constructor and copy assignment operator for each derived class will swell the class, and if new derived classes are added in the future, new member functions need to be added.
  • Step 4: Another approach is to add a member function template to the SmartPtr template, for example:
    • According to the following copy constructor, we can convert a SmartPtr<U> to SmartPtr<T> for any type T and any type U
    • The following copy constructor is not declared as explicit: because the conversion between primitive pointer types is implicit conversion, if our template type is primitive pointer, then we must support this implicit conversion, because we did not declare explicit
template<typename T>

class SmartPtr

{

public:

    //拷贝构造函数,是一个成员函数模板

    typename<typename U>
    
    SmartPtr(const SmartPtr<U>& other);

};

 

  • According to the above introduction, we can know that the purpose of designing a member function template for a class template is for uniformity and indirection, and to avoid redundant operations

Two, restrict the behavior of member function templates

  • In "One", we designed a copy constructor for the smart pointer class, so that type conversion can be performed according to the type
  • But there are still some problems that have not been resolved:
    • That is, for class inheritance, derived class pointers can be converted to base class pointers, but base class pointers cannot be converted to derived class pointers
    • Similarly, for ordinary types, we cannot convert int* to double*
  • Therefore, even if we design the member function template, then we still need to consider some special cases of conversion (listed above)

Solution

  • We can provide a get() member function similar to shared_ptr for our smart pointer class, this function returns the original pointer encapsulated by the smart pointer lock
  • The designed code is as follows:
template<typename T>

class SmartPtr

{

public:

    typename<typename U>

    SmartPtr(const SmartPtr<U>& other)

        :heldPtr(other.get())


    T get()const {

        return heldPtr;

    }

private:

    T* heldPtr;

};
  • The principle of the design here:
    • The get() member function returns the original pointer
    • In the copy constructor, we use the member initialization list to initialize the original pointer encapsulated by the smart pointer
    • Therefore, during the construction of the copy constructor, the conversion is performed based on the original pointer, so if the original pointer will judge this conversion behavior by itself: if it can be converted, then the copy constructor is executed correctly; if it cannot be converted, then the copy construction Function error

Three, design assignment member function template

  • The smart pointer template we designed above is not limited to constructors, but you can also design assignment operations yourself

Demonstration description

  • For example, shared_ptr:
    • Support all "from built-in pointer, shared_ptr, auto_ptr, weak_ptr" constructor
    • Support all the assignment operations above (except weak_ptr)
  • For example, the following is an excerpt from the source code of shared_ptr: (The template parameter strongly tends to use class instead of typename)
template<class T>

class shared_ptr

{

public:

    //下面都是拷贝构造函数(列出了一部分)

    template<class Y>

    explicit shared_ptr(Y* p);

    template<class Y>

    shared_ptr(shared_ptr<Y> const& r);

    template<class Y>

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

    template<class Y>

    explicit shared_ptr(auto_ptr<Y>& r);


    //下面都是赋值操作(列出了一部分)

    template<class Y>

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

    template<class Y>

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

};
  • Code description:
    • Constructor: All are explicit, except for the "generalized copy constructor". Because implicit conversion from a shared_ptr type to another shared_ptr is allowed, but implicit conversion from a built-in pointer or from other smart pointers to shared_ptr is not allowed (except using cast for coercive type conversion)
    • auto_ptr: The parameter is the copy constructor and assignment operator of auto_ptr, and its parameters are not const (As mentioned in clause 13, when you assign an auto_ptr, we hope that the objects it manages will be moved and changed)

Fourth, the difference with the default function

  • We have said that if a class does not provide a constructor, a copy constructor, or a copy assignment operator, the compiler will automatically provide a synthetic/default version for the class. This rule also applies to template classes.
  • So, for example, we added a member function template (copy constructor) to our class above, so when we use the copy constructor, which version is called? The answer is: choose according to the actual call situation
  • Therefore, if we design the member function template ourselves, we also need to copy the synthetic version provided by the class for ourselves, and design the non-member function template ourselves when necessary.
  • For example, the following is an excerpt from the source code of shared_ptr:
template<class T>

class shared_ptr

{

public:

    //拷贝构造函数

    shared_ptr (shared_ptr const& r); //非泛化版本

    template<class Y>

    shared_ptr(shared_ptr<Y> const& r); //泛化版本


    //拷贝赋值运算符

    shared_ptr& operator=(shared_ptr const& r); //非泛化版本

    template<class Y>

    shared_ptr& operator=(shared_ptr<Y> const& r); //泛化版本

};

Five, summary

  • Please use member function templates to generate functions that "accept all compatible types"
  • If you declare member templates to be used for "generalized copy construction" or "generalized assignment operation", you still need to declare the normal copy constructor and copy assignment operator

Guess you like

Origin blog.csdn.net/www_dong/article/details/113826923