• Effective C++ 笔记 —— Item 45: Use member function templates to accept "all compatible types."


    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: 
        explicit SmartPtr(T *realPtr); // smart pointers are typically initialized by built-in pointers 
        // ...
    };
    
    SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle); // convert SmartPtr<Middle> ⇒ SmartPtr<Top>
    
    SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom); // convert SmartPtr<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 and SmartPtr as completely different classes.

    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>
        SmartPtr(const SmartPtr<U>& other); // member template for a "generalized copy constructor"
    }

    This says that for every type T and every type U, a SmartPtr can be created from a SmartPtr, because SmartPtr has a constructor that takes a SmartPtr 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 from a SmartPtr) — 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 from a SmartPtr, but we don't want to be able to create a SmartPtr from a SmartPtr, 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 from a SmartPtr, because there is no corresponding implicit conversion from double* to int*. 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)
            : heldPtr(other.get())  // initialize this held ptr with other’s held ptr
        {
            // ...
        }
    
        T* get() const { return heldPtr; }
        //...
    
    private:
        T *heldPtr; // built-in pointer held by the SmartPtr
    };

    We use the member initialization list to initialize SmartPtr's data member of type T* with the pointer of type U* held by the SmartPtr. 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 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 any compatible built-in pointer,
        explicit shared_ptr(Y * p); 
    
        template<class Y> // 
        shared_ptr(shared_ptr<Y> const& r); // shared_ptr, or
    
        template<class Y> 
        explicit shared_ptr(weak_ptr<Y> const& r); // weak_ptr, or
    
        template<class Y>
        explicit shared_ptr(auto_ptr<Y>& r); // auto_ptr
    
        template<class Y> // assign from any compatible
        shared_ptr& operator=(shared_ptr<Y> const& r); // shared_ptr or
    
        template<class Y>
        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 nontemplate), 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 generalized
    
        template<class Y> // 
        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.
  • 相关阅读:
    linux开发基本库
    Configure,Makefile.am, Makefile.in, Makefile文件
    sql的其他语句
    sql 链接 查询
    sql的 select
    SQL语句:
    angular 指令系统(二)自定义指令:
    angularJS 指令系统
    angular JS 过滤器
    angularJS 控制器
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15950961.html
Copyright © 2020-2023  润新知