您的位置:首页 > Web前端

effective C++ 学习(Templates and Generic Programming(泛型编程))

2017-11-10 14:18 741 查看

effective C++ 学习(Templates and Generic Programming(泛型编程))

Item 41: Understand implicit interfaces and compile-time polymorphism

1.     In object-oriented, solvingproblems is achieved by explicit interfaces and runtime polymorphism. Anexplicit interface has an explicit function sign. Runtime polymorphism isconcerned about dynamic type of a class, and it is virtual function.

2.     In the template and genericprogramming, implicit interfaces and compile-time polymorphism is more important.As to implicit interfaces, they are decided by the operators. As tocompile-time polymorphism, it is reflected when template is instantiated. 

Item 42: Understand the two meanings of typename.

1.     Firstly the following twodeclarations are equal.

template<class T> class Widget;

template<typename T>class Widget;

However this doesn’t represent “class” and “typename” are equivalent.

2.     Then we analyze the followingcode.

template<typename C>
void print2nd(const C&container)
{
   if(container.size()>= 2)
   {
       C::const_iteratoriter(container.begin());
       ++iter;
       int value= *iter;
       std::cout<<value;
   }
}
Note: first of all, we need to know fewconcepts:

1)     Dependent names (从属名称): the name in the template dependent on a template parameter;

2)     Nested dependent name (嵌套从属名称): If the dependent names are nested in the class, we name it nesteddependent name; for example, C::const_iterator.

3)     Non-dependent names(非从属名称): it doesn’t dependent on template parameters, like “int”.

Next we discuss the questions thatdependent names bring. It may be difficult for compiler to parse the dependentnames, like the following.

template<typename C>
void print2nd(const C& container)
{
    C::const_iterator*x;
}
Under the condition of knowingC::const_iterator is a type for us, we know it defined a pointer x. However forcompiler, it thinks C::const_iterator is not a type but a member of a class andx may be a variable. As a consequence, the sentence is thought as
amultiplication expression.

Note: under default conditions, C++ thinksthe nested dependent name is not a type but you tell it, in other words, youshould add “typename” in the front of “C::const_iterator”. For the g++, theseare truth, however for Microsoft compiler, this problem doesn’t
exist, in otherwords, we don’t have to add the typename in visual studio.

3.     You should not use typename fornested dependent name under some cases. For example.

template<typename T>

4000
class Derived:
publicBase<T>::Nested
{
public:
  explicitDerived(int x)
     : Base<T>::Nested(x)
  {
     typenameBase<T>::Nested temp;
  }
};
Note: if the nested dependent name (嵌套从属类型名称) is in base classes list or member initialization list, we shouldnot use typename.

4.     There is an example about usingtypename:

template<typename IterT>
voidworkWithIterator(IterT iter)
{
    typename std::iterator_traits<IterT>::value_typetemp(*iter);
}
std::iterator_traits<IterT>::value_typereturn the type of iter containing data. If you think it is too long, you canuse typedef to get a new type.

typedef
typenamestd::iterator_traits<IterT>::value_type Value_type;

Item 43: Know how to access names in templatized baseclasses.

1.     The inheritances in ObjectOriented C++ and Template C++ are different, and in Object Oriented C++,compiler would search names inherited from base classes, but in Template C++,it does not. Of course, it is different depending on different compilers.

#include<iostream>
class MsgInfo{};
class CompanyA
{
public:
    void sendClearText(conststd::string& msg)
    {
       std::cout<<"CompanyA::sendClearText"<<std::endl;
    }
    void sendEncrypted(conststd::string& msg)
    {
       std::cout<<"CompanyA::sendEncrypted"<<std::endl;
    }
};
class CompanyB
{
public:
    void sendClearText(conststd::string& msg)
    {
       std::cout<<"CompanyB::sendClearText"<<std::endl;
    }
    void sendEncrypted(conststd::string& msg)
    {
       std::cout<<"CompanyB::sendEncrypted"<<std::endl;
    }
};
class CompanyZ
{
public:
    void sendEncrypted(conststd::string& msg)
    {
       std::cout<<"CompanyZ::sendEncrypted"<<std::endl;
    }
};
 
template <typename Company>
class MsgSender
{
public:
    void sendClear(constMsgInfo& info)
    {
       std::stringmsg;
       Companyc;
       c.sendClearText(msg);
    }
    void sedSecret(constMsgInfo& info)
    {
       std::stringmsg;
       Companyc;
       c.sendEncrypted(msg);
    }
};
template<>
classMsgSender<CompanyZ>
{
public:
    void sendSecret(constMsgInfo& info)
    {
       std::stringmsg;
       CompanyZc;
       c.sendEncrypted(msg);
    }
};
template<typename Company>
classLoggingMsgSender:
publicMsgSender<Company>
{
public:
    void sendClearMsg(constMsgInfo& info)
    {
       std::cout<<"write information before transfering"<<std::endl;
       sendClear(info);
       std::cout<<"write information after transfering"<<std::endl;
    }
};
int main()
{
    LoggingMsgSender<CompanyB>logMsgSender;
    MsgInfoinfo;
    logMsgSender.sendClearMsg(info);
    return 0;
}
Note: In Microsoft compiler, the code abovecan run normally, but in g++ there is error, as following:

“error: there are no arguments to‘sendClear’ that depend on a template parameter, so a declaration of‘sendClear’ must be available”

Next we explain the question by twoexamples in detail:

1)     If there is a company that don’thave “sendClearText”; There is the following error:

Figure1 under Microsoft compiler
2)     If there is specialized “MsgSender”which have the “sendClear”, as following:

template<>
class MsgSender<CompanyZ>
{
public:
   voidsendSecret(const MsgInfo& info)
   {
       std::string msg;
       CompanyZ c;
       c.sendEncrypted(msg);
   }
};
Note: this is a total template specialization.“template<>” represents a total template specialization. When realCompanyZ is used, the total template specialization will be used, in otherwords the specialized template has higher priority. There are the following
twopoints about template specialization:

1)     We can only specialize a partof template class;

2)     We can only specialize a partof template parameters (partial specialization);

template <class T>
struct remove_reference
{
   typedef Ttype;
};
template <class T>struct remove_reference<T&>
{
   typedef Ttype;
}
template <class T>struct remove_reference<T&&>
{
   typedef Ttype;
}
The last two are partial specializations,they are left value reference and right value reference respectively.

Figure2 under Microsoft compiler
When the template real parameter isCompanyZ, the specialized templates will be used due to the higher priority of specialization.Therefore compiler does not search names of base classes.

2.     The solutions about that:

1)     Using “this->”;

2)     Using “using ” declaration;

3)     Using “MsgSender<Company>::sendClear(info)”;

The third caseis not a good solution, because an explicit qualification would close “Virtualbinding behavior ”.

3.     About the different treatmentto template base from Microsoft and g++, the former use a later treatment way(when the template is instantiated) but the latter choose to deal with earlier(when parsing defination of derived class template).

Item 44: Factor parameter-independent code out oftemplates

1.     Solve the code bloat (代码膨胀) problem by commonality and variability analysis (共性和变性分析). The code bloat happen in the object code.

template<typename T,std::size_tn>
class SquareMatrix
{
public:
   voidinvert();
};
SquareMatrix<double, 5> sm1;
sm1.invert();
SquareMatrix<double,10> sm2;
sm2.invert();
Note: the invert()s of sm1 and sm2 are samebut the number of 5 and 10.

First modification:

class SquareMatrixBase
{
protected:
   voidinvert(std::size_t matrixSize);
};
template <typename T,std::size_t n>
class SquareMatrix:
privateSquareMatrixBase<T>
{
private:
   usingSquareMatrixBase<T>::insert;
public:
   voidinvert(){this->insert(n);}
};
Note: there are some of the followingquestions:

1)     The base class is instantiatedonly by the element type of matrix, so all instances with the same element typeand different sizes share a unique base class and then to relieve the problemof derived class code repetition;

2)     Using inline calling make theextra cost of calling be almost 0;

3)     “this->” avoid the “insert”of derived class covering the “insert” of base class;

4)     “private inheritance” impliesit is not a relationship of “is - a” but “is-implemented-in-terms-of”.

2.     How to know the operated data. Onesolution:

template<typename T>
classSquareMatrixBase
{
protected:
    SquareMatrixBase(std::size_tn, T* pMem)
       :size(n),pData(pMem){}
    void setDataPtr(T* ptr){pData = ptr;}
    void invert(std::size_t matrixSize){}
private:
    std::size_tsize;
    T*pData;
};
 
template <typename T, std::size_t n>
classSquareMatrix:
private SquareMatrixBase<T>
{
private:
    using SquareMatrixBase<T>::invert;
public:
    SquareMatrix():SquareMatrixBase<T>(n,data){}
    void invert(){this->insert(n);}
private:
    T data[n*n];
};
Note: there are some of valuable problems:

1)     The base class store thepointer pointing data can be convenient for other functions that need tooperate data.

2)     The “T data[n*n]” is legal intemplate but illegal in object-oriented.

3)     The way will lead to a toolarge object.

3.     Dynamic allocation of memory:

template<typename T,std::size_t>
class Squarematrix:
privateSquarematrixBase<T>
{
public:
   SquareMatrix():SquareMatrixBase<T>(n,0), pData(new T[n*n])
   {
       this->setDataPtr(pData.get());
   }
private:
   boost::scoped_array<T> pData;
};
4.     Extracting factorparameter-independent code out of template makes matrixes of different sizeshave unique “invert” and then to reduce size of “exe” file and then to reducesize of
113f1
working set (working set is a group of pages on which a process runsunder
virtual environment). However this way might also increase size ofobject.

5.     Non-type template parameterscan bring code bloat, meanwhile, type template parameters also do. Eg. The intand long is equal in some compiler. In addition, in the most compilers, allpointers have the same binary representations (二进制表示), which also
brings code bloat. Of course, we can solve theproblems by making each member function use the unique function implementation,in other words making their instantiation types share implementation code, suchas making them call untyped pointers functions.

Item 45: Use member function templates to accept “allcompatible types”.

1.     The different instantiationswith the same template don’t have congenital relationship, as following:

class Top{};
class Middle:public Top{};
class Bottom:public Middle{};
 
Top*pt1 = new Middle;
Top*pt2 = new Bottom;
const Top* pct2 = pt1;
 
template<typename T>
class SmartPtr
{
public:
explicitSmartPtr(T* realPtr);
};
 
SmartPtr<Top>pt1 = SmartPtr<Middle>(new Middle);
SmartPtr<Top>pt2 = SmartPtr<Bottom>(new Bottom);
SmartPtr<const Top> pct2 = pt1;
Note: the SmartPtr<Middle> andSmartPtr<Top> is totally different, unlike the relationship betweenMiddle* and Top*.

2.     Next what we will do is how toimplement the transformation. Constructor function template will be a goodsolution, as following:

template<typename T>
class SmartPtr
{
public:
   explicitSmartPtr(T* realPtr);
   template<typename U>
   SmartPtr(constSmartPtr<U>& other);
};
Note: the copy constructor denotes the typeof SmartPtr<U> can generate the type of SmartPtr<T>. Theconstructor function is limited by explicit, which represents it supportsimplicit conversion and it is rational due to conforming to real pointer. Sometimeswe
name it generalized copy constructor (泛化复制构造函数).

3.     We need to save origin pointer.

template<typename T>
class SmartPtr
{
public:
   explicitSmartPtr(T* realPtr);
   template<typename U>
   SmartPtr(constSmartPtr<U>& other):heldPtr(other.get()){}
 
   T* get() const{return heldPtr;}
private:
   T* heldPtr;
};
Note: the purpose of the version is toimplement the polymorphism in object-oriented.

4.     Consider the following code:

class Top{
public:
virtual
void test()const{}
};
class Middle:public Top{
public:
   virtual
void test() const{std::cout<<"Middle"<<std::endl;}
};
class Bottom:public Middle{
public:
   virtual
void test() const{std::cout<<"Bottom"<<std::endl;}
};
 
Top*pt1 = new Middle;
Top*pt2 = new Bottom;
const Top* pct2 = pt1;
 
template<typename T>
class SmartPtr
{
public:
   explicitSmartPtr(T* realPtr):heldPtr(realPtr){}
   template<typename U>
   SmartPtr(constSmartPtr<U>& other):heldPtr(other.get()){}
   constSmartPtr<T>&
operator =(const SmartPtr<T>& parameter)
   {
       this->heldPtr= parameter.heldPtr;
       return *this;
   }
   T* get() const{return heldPtr;}
private:
   T* heldPtr;
};
 
int main()
{
SmartPtr<Top>pt1(SmartPtr<Middle>(new Middle));
SmartPtr<Top>pt2 = SmartPtr<Bottom>(new Bottom);
pt1.get()->test();
pt2.get()->test();
pt1= pt2;
pt1.get()->test();
SmartPtr<const Top> pt3 = pt2;
pt3.get()->test();
}



Note:

1)     the operator = will call copyconstructor in the declaration sentence but call operator = function in thenon-declaration sentence.

2)     The const behind the name ofthe function is also part of the function sign.

5.     It is fantastic for memberfunction templates not to change the basic language rules. The generalized copyconstructor doesn’t affect the normal copy constructors, as following:

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);
};

Item 46: Define non-member function inside template whentype conversions are desired.

1.     Firstly obverse the followingprevious code:

class Rational
{
public:
    Rational(int number1=0,intnumber2=1)
       :num1(number1),num2(number2){}
    const
int getNum1()const{return num1;}
    const
int getNum2()const{return num2;}
private:
    int num1,num2;
};
const Rational
operator*(constRational& lhs,const Rational& rhs)
{
    return Rational(lhs.getNum1()*rhs.getNum1(),lhs.getNum2()*rhs.getNum2());
}
int main()
{
    Rationalrational(1,2);
    Rationalresult = rational*2;
    std::cout<<result.getNum1()<<std::endl;
    std::cout<<result.getNum2()<<std::endl;
    return 0;
}



Note: the constructor with two parametershave default values, so the number 2 can be converted into Rational. If youwrote the following code:

Rational result = rational*(2,3);
The result is:



Because the result (2, 3) return is 3, sothe values of num1 and num2 are 3 and 1 respectively.

However if the similar codes are used intemplate, errors will happen.

template<typename T>
class Rational
{
public:
    Rational(const T& number1=0,
constT& number2=1):num1(number1),num2(number2){}
    const T getNum1()
const{return num1;}
    const T getNum2()
const{return num2;}
private:
    Tnum1,num2;
};
template<typename T>
constRational<T>
operator(const Rational<T>&lhs,
const Rational<T>& rhs )
{
    return Rational<T>(lhs.getNum1()*rhs.getNum1(),lhs.getNum2()*rhs.getNum2());
}
int main()
{
    Rational<int> oneHalf(1,2);
    Rational<int> result = oneHalf*2;
    return 0;
}
Note: the code can’t pass compiler, becausein template inferring the type of real parameter can’t depend on implicit conversionfunctions.

2.     The solution about the problemabove: in function template we cannot call the implicit conversion functionsbut when the function templates are instantiated we can do that. Therefore wecan use friend function solve this problem.

template<typename T>
class Rational
{
public:
    Rational(const T& number1=0,
constT& number2=1):num1(number1),num2(number2){}
    const T getNum1()
const{return num1;}
    const T getNum2()
const{return num2;}
 
    friend
const Rationaloperator*(constRational& lhs,
const Rational& rhs)
    {
       return Rational(lhs.getNum1()*rhs.getNum1(),lhs.getNum2()*rhs.getNum2());
    }
private:
    Tnum1,num2;
};
 
int main()
{
    Rational<int> oneHalf(1,2);
    Rational<int> result = oneHalf*2;
    std::cout<<result.getNum1()<<std::endl;
    std::cout<<result.getNum2()<<std::endl;
    return 0;
}



3.     We can also put theimplementation code on the outside of class, especially when the code is long.

template<typename T>
classRational;
template<typename T>
constRational<T> doMultiply(constRational<T>& lhs,
constRational<T>& rhs)
{
returnRational<T>(lhs.getNum1()*rhs.getNum1(), lhs.getNum1()*rhs.getNum1());
}
template<typename T>
class Rational
{
public:
    Rational(const T& number1=0,
constT& number2=1):num1(number1),num2(number2){}
    const T getNum1()
const{return num1;}
    const T getNum2()
const{return num2;}
 
    friend
const Rationaloperator*(constRational& lhs,
const Rational& rhs)
    {
       return doMultiply(lhs,rhs);
    }
private:
    Tnum1,num2;
};
 
int main()
{
    Rational<int> oneHalf(1,2);
    Rational<int> result = 2*oneHalf;
    std::cout<<result.getNum1()<<std::endl;
    std::cout<<result.getNum2()<<std::endl;
    return 0;
}
Note: the doMultiply() don’t supportcombining multiply but the “friend operator*” do it. Therefore they have workrespectively. The “friend operator* ” can support implicit conversion and thenobtain two Rational<T>, which can support doMultiply() that complete
thereal work.

Item 47: Use traits classes for information about types.

1.     There are 5 categories ofiterators:

1)     Input iterator: only read, moveforward and only read once. Eg. istream_iterators. One-pass algorithms(一次性操作算法).

2)     Output iterator: only write,move forward and only write once. Eg. ostream_iterators. One-pass algorithms.

3)     Forward iterator: can read orwrite more than once, multi-pass algorithms (多次性操作算法).

4)     Bidirectional iterator: canmove in both directions, eg. the iterators of list, set, multiset, map andmultimap and so on.

5)     Random access iterator: canmove any distance forward and backward in constant time. Eg. the iterators ofvector, deque and string.

structinput_iterator_tag
    {   // identifying tagfor input iterators
    };
structoutput_iterator_tag
    {   // identifying tagfor output iterators
    };
structforward_iterator_tag
    : public input_iterator_tag, output_iterator_tag
    {   // identifying tagfor forward iterators
    };
structbidirectional_iterator_tag
    : public forward_iterator_tag
    {   // identifying tagfor bidirectional iterators
    };
Note: they is the relationship of “is-a”.

2.     We need to use traits classesfor information about types. There are the following problems:

1)     Its performance should be samebetween user-defined and built-in types;

2)     The choice should be done atcompiling.

template <...>
class deque
{
public:
    class iterator
    {
    public:
       typedef random_access_iterator_tag iterator_category;
    }
};
template<typename IterT>
struct iterator_traits
{
    typedef
typenameIterT::iterator_category iterator_category;
};
template<typename IterT>
structiterator_traits<IterT*>
{
    typedef random_access_iterator_tag iterator_category;
};
Note: on the one hand, the “iterator_category”needs to be declared in the user-defined types; on the other hand, to surebuilt-in objects (they are pointers) to be treated equally, the template wasspecialized partly.

template<typename IterT,
typenameDistT>
voiddoAdvance(IterT& iter, DistT d,
    std::random_access_iterator_tag)
{
    iter +=d;
}
template<typename IterT,
typenameDistT>
voiddoAdvance(IterT& iter, DistT d,

    std::bidirectional_iterator_tag)
{
    if(d>=0){while(d--)++iter;}
    else{
while(d++)--iter;}
}
template<typename IterT,
typenameDistT>
void doAdvance(IterT&iter, DistT d,
    std::input_iterator_tag)
{
    if(d<0)
    {
       throw std::out_of_range("Negativedistance");
    }
    while(d--) ++iter;
}
template<typename IterT,
typenameDistT>
voidadvance(IterT& iter, DistT d)
{
    doAdvance(iter,d,
       typename std::iterator_traits<IterT>::iterator_category())
}
Note: achieving operate choices atcompiling by overloading function.

Item 48: Be aware of template metaprogramming.

1.     The advantages about TMP:

1)     Make something easier;

2)     It can transfer works fromrunning to compiling. On the one hand, we can find faults earlier. On the otherhand, we can achieve smaller executable file, shorter running time and smallermemory.

For 1), forexample:

#include<iostream>
#include<list>
 
voidadvance(std::list<int>::iterator&iter,
int d)
{
    if(typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category)
       ==typeid(std::random_access_iterator_tag))
       iter+= d;
    else
    {
       if(d>=0){while(d--)++iter;}
       else   {while(d++) --iter;}
    }
}
 
int main()
{
    std::list<int>::iterator iter;
    advance(iter,10);
    return 0;
}
Note: the place where sentence of “iter+= d;” tie on will appear mistakes, because
bidirectional iterators doesn’t support“+=”, even if we know when it is
bidirectionaliterators the sentence of “iter+=d” won’t executed. Because compiler shouldensure every sentence will be available. Under this situation, the TMP plays anirreplaceable role.

2.     Recursive templateinstantiation (递归模板具现化) for template.

#include<iostream>
 
template<unsigned n>
struct Factorial
{
    enum {value = n * Factorial<n-1>::value};
};
template<>
structFactorial<0>
{
    enum {value = 1};
};
int main()
{
    Factorial<10>temp;
    std::cout<<temp.value<<std::endl;
    return 0;
}



Note: where “template<>
structFactorial<0> {  enum {value = 1};};” is atotal specialization (全特化).

3.     There are the following threeadvantages in TMP:
1)     Ensure the accuracy ofmeasurement units;
2)     Optimize matrixcalculation.  using less memory,achieving faster speed.
Achieve custom designpattern; is fundament of generative programming. In other words, TMP cangenerate customized code based on combinations of policy choices.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: