"Effective C + +": Clause 44-clause 45

Source: Internet
Author: User
Tags class generator repetition

    • Article 44 extracting parameter-independent code from templates
    • Article 45 using the member function template to accept all compatible types
      • Templates and generics programming

Article 44: Extracting parameter-independent code from templates

Templates can save time and avoid code duplication. For similar classes or functions, you can write a class template or function template that allows the compiler to do the rest. Doing so can sometimes lead to code bloat: the binary code with repeated (or almost repetitive) codes, data, or both. But this time the source code may look neat.

First, learn a noun: commonality and degeneration analysis (commonality and variability analyses). Relatively easy to understand. For example, you're writing a few functions that use the same code, and you tend to move the same code into a new function and call several other functions. Similarly, if you write a class, some of which are the same as the other classes, you won't be able to write the same parts again, just move the common part into the new class and use inheritance or compositing (* * clause **32,38,39), Let the original classes use these common characteristics, the original classes of the cross-section (variation part) is still left in the original position does not move.

When writing templates, do the same analysis to avoid duplication. The repetition in the Non-template code is clear: you can see that there is a repetition between the two functions or the classes. But in the template code, repetition is ambiguous, because there is only one copy of the template source.

For example, you intend to write a template for a square matrix of fixed dimensions, which has a function that supports inverse matrix operations

template<typenamestd::size_t n>//T为数据类型,n为矩阵大小    class SquareMatrix{    public:        ……        void invert();//求逆运算    };    SquareMatrix<double,5> sm1;    sm1.invert();//调用SquareMatrix<double,5>::invert    SquareMatrix<double,10> sm2;    sm2.invert();//调用SquareMatrix<double,10>::invert

The above will materialize two copies of invert. The two functions are almost identical (except for an operation 5*5 matrix, an operation 10*10). This is a typical example of code bloat.

The above two functions are the same except that the operation matrix is different in size. Instead of repeating the code, you can create a function with a value for it. So there was the First Amendment to Squarematrix:

 Template<TypeNameT>classsquarematrixbase{protected:voidInvertSTD:: size_t matrixsize); ...... };Template<TypeNameTSTD:: size_t n>classSquarematrix:Privatesquarematrixbase<t>{Private:usingSquarematrixbase<t>::invert ();//code obscure base invert,** clause **33     Public: ......voidInvert ()//Beg inverse{ This->invsert (n);//Explain later why this is used}    };

Squarematrixbase::invert is just trying to avoid derived classes code duplication, so it replaces public with protected. This function uses THIS->, because the function names within the templated base class are masked by derived classes ( clause **43). Note that the inheritance relationship between Squarematrixbase and Squarematrix is private, which means that the base class is intended to help derived classes implementation, and that the two are not **is-a relationships.

There is now another problem, where is the data for the Squarematrixbase::invert operation? It is in the parameter until the matrix size, but the matrix data derived class is known. How do I contact derived class and base class? One approach is to add a parameter (such as a pointer) to Squarematrixbase::invert. This will work, but with other factors in mind (for example, there are other functions in squarematrixbase, you can also manipulate the data), you could add this pointer to the Squarematrixbase class.

    template<typename T>    class SquareMatrixBase{    protected:        SquareMatirxBase(std::size_t n,T* pMem)        :sizepData(pMem){}        void setDataPtr(T* ptr) {pData=ptr;}        ……    private:        std::size_t size;        T* pData;    };    template<typename T, std::size_t n>    class SquareMatrix:private SquareMatrixBase<T>{    public:        SquareMatrix()        :SquareMatrixBase<T>(n, data){}        ……    private:        T data[n*n];    };

Objects of this type do not need to allocate memory dynamically, but the object itself can be very large. Another approach is to put the matrix data into the heap

 Template<typename T , Span class= "Hljs-symbol" >STD:  :size_t  n> class  squarematrix : private  squarematrixbase  < t   >{ public:  squarematrix  ()  Span class= "Hljs-symbol" >:SQUAREMATRIXBASE<T>  (n, 0 ), PData (new t  [N*n]) {this->setdataptr (Pdata.get ());}    ......        private:  boost:     :SCOPED_ARRAY<T>  pData; };

In this way, derived classes of the same type will share the base class. For example, Squarematrix

Article 45: Use the member function template to accept all compatible types

In the template, the class after the materialized template parameter does not have a derivation relationship because of the materialized type. Take a look at an example of pointers. True pointers support implicit conversions (implitic conversions), derived class pointers can be implicitly converted to base class pointers, pointers to Non-const objects can be converted to pointers to const objects, and so on. For example:

    Top{……};    Top{……};    Bottom:public Middle{……};    Top* pt1=new Middle;//Middle* 转换为Top*    TopBottom;//Bottom* 转换为Top*    Top* pct2=pt1;//TopTop*

If you use a template to define a smart pointer, the conversion above is a bit cumbersome.

    template<typename T>    class SmartPrt{    public:        explicit SmartPtr(T* realPtr);        ……    };    SmartPtr<Top> pt1=SmartPtr<Middle>(new Middle);//SmartPtr<Middle>转换为SmartPtr<Top>    SmartPrt<Top> pt2=SmartPrt<Bottom>(new Bottom);    SmartPrt<const Top> pct2=pt1;

There is no relationship between the different materialization of the same template, even if there are inheritance and derivation relationships between the two types that are materialized. The compiler treats smartptr and smartptr as completely different types of classes. In order for the above code to be compiled, the ability to convert between SMARTPTR classes must be explicitly written.

Templates and generics programming

To implement the transformation, you can do it in the constructor of the smart pointer, but if the derived class has a continuation derivation, then the constructor is added, which is obviously unreasonable. Therefore, what we need is not simply to write a constructor for Smartptr, but to compose a construction template. Such a template is called the member function template (abbreviated member templates), which functions as a class generator

    template<typename T>    class SmartPrt{    public:        template<typename U>        SmartPtr(const SmartPrt<U>& other);//member template,为了生成copy cotr        ……    };

The above code means that for any type T and any type U, a smartptr can be generated based on Smartprt . Copy COTR is not declared as explicit, because the conversion may be implicit.

This generalization constructor, written for Smartptr, provides more than we need. We want to create a smartprt based on a smartptr, but do not want to create a smartptr based on a smartptr because it is contradictory to the public inheritance.

The above code is not complete and the copy COTR is not implemented in Smartptr. Assuming that smartptr, like Auto_ptr and Tr1::shared_ptr, provides a GET member function that returns a smart pointer object, you can constrain the transformation behavior in the construction template

    template<typaname T>    class SmartPtr{    public:        template<typename U>        SmartPrt(const SmartPrt<U>& other)        :heldPrt(other.get()){};        getconst{return heldPrt;}        ……    private:        T* heldPrt;    };

In the preceding code, there is an implicit conversion: convert U * to t*, which limits the conversion behavior.

member function templates function is not only the constructor, but also an important function is to support the assignment operation. For example, TR1 's shared_ptr supports all construction behavior from compatible built-in pointers, Tr1::shared_ptrs, Auto_ptrs, and Tr1::weak_ptrs, as well as assignment operations from the above-mentioned objects (except Tr1::weak_ptr). Take a look at an excerpt from the TR1 specification about tr1::shared_ptr.

    Template<classT>class shared_ptr{ Public:Template<classY>Explicit shared_ptr(y* p);Template<classY>shared_ptr(shared_ptr<Y>Const& R);Template<classY>Explicit shared_ptr(weak_ptr<y>Const& R);Template<classY>Explicit shared_ptr(auto_ptr<Y>Const& R);Template<classY>shared_ptr&operator=(shared_ptr<Y>Const& R);Template<classY>shared_ptr&operator=(auto_ptr<Y>Const& R); ...... };

In addition to the generalization copy constructor, the other constructors are explicit, which means that the implicit conversion of the shared_ptr type is allowed, but the implicit conversion from other smart pointers to shared_ptr is not allowed.

member function templates does not change the basic language rules, and the compiler produces a copy constructor and copy assignment does not conflict. Tr1:shared_ptr declares a generalized copy constructor, and if T and Y are the same, the generalized copy constructor is materialized as a normal copy constructor. The compiler will secretly generate a copy constructor for Tr1::shared_ptr? Or when the Tr1::shared_ptr object expands the construction behavior based on another Tr1::shared_ptr object of the same type, the compiler materializes the generalized copy constructor template?

Member templates does not change the language rules, if the program needs a copy constructor and you do not declare it, the compiler will generate it for you. Declaring a generalization copy constructor within class does not prevent the compiler from generating their own copy constructor (Non-template). If you want to control every aspect of the copy constructor, declare the normal copy constructor. The same rules apply to assignment assignment operations.

Summary
-Use member function templates (member function template) to generate "acceptable all compatible types" functions.
-If you declare member templates used to generalize the copy constructor or generalization assignment operation, still declare the normal copy constructor and copy assignment operator.

"Effective C + +": Clause 44-clause 45

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.