代码改变世界

C++Primer 第十六章

2016-06-29 21:40  szn好色仙人  阅读(225)  评论(0编辑  收藏  举报
//1.模板定义以关键字template开始,后跟一个模板参数列表,此列表不能为空。编译器用推断出的模板参数来实例化一个特定版本的函数。类型参数前必须使用class或者typename(推荐使用typename)。
template <typename T> bool comp(T t0, T t1){return t0 > t1;}

//2.除了定义类型参数外,还可以在模板中定义非类型参数。一个非类型参数表示一个值(必须是常量表达式,实际使用过程中,最好只使用整形,而不要使用其他类型)而非一个类型,通过特定的类型名而非typename来指定非类型参数。
template<int M> inline int fun(int (&p)[M]){return sizeof p;}    //注意inline的位置
int a[10] = {};
int v = fun(a);    //v = 40

//3.当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。这一特性影响了我们如何组织代码以及错误何时被检测到。
//  与一般代码不同,模板的头文件通常既包含声明,也包含定义。

//4.当调用一个函数模板的时候,编译器通常用函数实参为我们推断模板实参。
//  与函数模板的不同之处是,编译器不能为类模板推断模板参数类型,为了使用类模板,必须在模板名后的尖括号中提供额外信息,用来代替模板参数的模板实参列表(函数模板也能这么做)。
//  由以上可知,类模板的名字不是一个类型名,类模板用来实例化类型,而一个实例化的类型总是包含模板参数的。
//  因为类模板的名字不是类型名,而类模板名字加上尖括号以及显示模板实参才是一个类型名,所以在模板类外定义模板类的成员函数的时候必须以关键字template开始,后接类模板参数列表。
template<typename T>
class CA
{
public:
    CA(){}
public:
    T GetValue();
    void SetValue(T t){value = t;}
private:
    T value;
};
template<typename T> T CA<T>::GetValue()    {return value;}

CA<int> A;
A.SetValue(10);
int value = A.GetValue();    //value = 10

//5.默认情况下,一个类模板的成员函数只有当程序用到它的时候才进行实例化。如果一个类模板的成员函数没有被用到,则此成员函数将不被实例化。
//  上述特性使得:即使某种类型不能完全符合模板的要求,我们仍能使用该类型实例化类。

//6.当我们使用一个类模板类型时必须提供模板实参,但这个规则有一个例外:在类模板自己的作用域中,我们可以直接使用模板名,而不提供实参。

//7.模板和友员
//  模板类与模板友元函数:
//    A.类为模板,友元函数也是模板,且使用的模板参数一致。                则此友元函数仅对相同模板参数的类实例具有特殊访问权限。
//    B.类为模板,友元函数不是模板(使用指定模板实参类型的类实例)。         则友元函数只对指定类型的模板参数的类实例具有特殊访问权限。
//    C.类为模板,友元函数也是模板,且使用的模板类型参数不一致。           则此友元函数对所有类实例都具有特殊访问权限。
//    D.类不是模板,友元函数是模板。                                   则所有模板类型的友元函数都是对此类具有特殊访问权限。
//  模板类有模板类的友元关系(在A类中声明B类为其友元类)
//    a.A类为模板, B类是模板,且A类和B类使用的模板参数一致               则此B类仅对相同模板参数的A类实例具有特殊访问权限。
//    b.A类是模板, B类是模板,且A类和B类使用的模板参数不一致。           则所有B类的实例都对A类具有特殊访问权限
//    c.A类是模板, B类不是模板。                                     则B类对所有A类的实例均具有特殊访问权限
//    d.A类不是模板,B类是模板,且B类使用了指定模板实参类型。              则只有指定类型的B类实例才对A类具有特殊访问权限
//    e.A类不是模板,B类是模板,且B类没有使用固定的模板类型。              则所有B类的实例都对A类具有特殊访问权限
template<typename T>
class CMyTest
{
private:
    T value;
public:
    T GetValue()    {return value;}

    /*A结论*/friend void SetVlaue_10(CMyTest<T>& MyTest)        {MyTest.value = 10;}
    /*结论B*/friend void SetValue_20(CMyTest<int> &MyTest);//注意点:此类型的友员函数不能定义在类的内部,否则会造成重定义                
    /*结论C*/template<typename X> friend void SetValue(CMyTest/*<T> 这里的<T>可加可不加*/ &temMyTest, X value)    {temMyTest.value = value;}
};
void SetValue_20(CMyTest<int> &MyTest)    {MyTest.value = 20;}

//前向声明
template<typename T>class CMyTest2;
class CMyTest1
{
public:
    void PrintfValueInt()    {printf("%d\n", valueInt);}
private:
    int valueInt;

    /*结论d*/friend class CMyTest2<CMyTest1>;
    /*结论e*/template<typename T> friend class CMyTest3;        //注意这里的friend的位置
};

template<typename T>
class CMyTest2
{
public:
    void SetCMyTest1ValueInt_1000(T& a)    {a.valueInt = 1000;}

    /*结论a*/friend class CMyTest3<T>;
private:
    int value;
};


template<typename T>
class CMyTest3
{
public:
    void SetCMyTest1ValueInt_3000(CMyTest1& a)    {a.valueInt = 3000;}
    void SetValue_10(CMyTest2<T>& temMyTest)    {temMyTest.value = 10;}

private:
    T value;

    /*结论c*/friend class CMyTest4;

};

class CMyTest4
{
public:
    template<typename T>void SetValue(CMyTest3<T>& temTest, T value)    {temTest.value = value;}
    /*结论D*/template<int X> friend void SetValue(CMyTest4 &MyTest)        {MyTest.value = X;}
private:
    int value;
};

template<typename T>
class CA
{
    template<typename X> friend class CB;
private:
    int value;
};

template<typename X> class CB
{
    template<typename T>
    /*结论b*/void Fun(CA<T> a)    {a.value = 10;}
};

//8.可以将模板类型参数声明为友员,即使使用内置类型也不会出错。
template<typename T> class CA
{
    friend T;
public:
    void SetValue(T v){value = v;}
private:
    T value;
};

class CB
{
public:
    CB(){}
    CB(int v) : value(v){}
public:
    void Fun(CA<CB>);
public:
    int value;
};
void CB::Fun(CA<CB> a)    {printf("%d\n", a.value.value);}

CB b;
CA<CB> a;
a.SetValue(10);
b.Fun(a);        //输出10

//9.类模板的静态成员:相同类型的实例化后的类共同维护一个类的静态成员。不同实例化的类之间的静态成员互不相关。

//10.模板参数遵循普通的作用域规则,一个模板参数名的可用范围是在其声明后,至模板声明或定义结束前。
//   与任何其他名字一样,模板参数会隐藏外层作用域中声明的相同名字。但是与大多数其他上下文不同,在模板内不能重用模板参数名。
//   由于模板参数名不能重用,所以一个模板参数名在一个特定的模板参数列表中只能出现一次。

//11.考虑在类模板中: T::size_type *p;由于编译器无法掌握T的类型,所以编译器不知道这句话的意思是正在定义一个名为p的指针还是将名为size_type的static数据成员与名为p的变量相乘。
//   默认情况下,C++假定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板类型参数的类型成员,就必须显示的告诉编译器该名字是一个类型。通过使用关键字typename来实现这一点。
//   由上述结论可知:  T::size_type *p;的默认意思是将静态成员size_type和p相乘。而typename T::size_type *p;则是定义了一个T::size_type类型的指针。
template<typename T> typename vector<T>::value_type fun()    {return vector<T>::value_type();}    //vector<T>::value_type();返回一个类型为T的值初始化的值
string s = fun<string>();    //s = "";

//12.可以为类模板提供默认模板实参。在新标准下,也可以为函数提供默认模板实参(VS2010不支持此项特性)。
//   无论何时使用一个模板,我们都必须在模板名之后加上尖括号。尖括号指出类必须从一个模板实例化而来。特别的,当一个类模板为其所有的模板参数均提供了默认实参,且我们希望使用这些默认实参,则必须在模板名后跟一个空尖括号对。

//13.一个类,无论是模板类还是非模板类,都可以包含本身是模板的成员函数。这种成员称为成员模板。
//   成员模板不能是虚函数。这是因为:编译器在处理一个类的时候,会希望确定其虚函数表,若允许定义成员模板为虚函数,则虚函数表无法在编译时就被确定,而必须根据成员模板的实例化情况确定。所以成员模板不能是虚函数。
//   与类模板的普通函数成员不同,成员函数是函数模板。当我们在类模板外定义一个成员模板的时候,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,后跟成员自己的参数列表。
template<typename T = int> class CA    {public: template<typename X> void fun();};
template<typename T> template<typename X> void CA<T>::fun(){printf("%d, %d\n", sizeof(T), sizeof(X));}
CA<> a;
a.fun<double>();    //输出4, 8

//14.当模板被使用时才会进行实例化这一特性意味着:相同的类实例可能出现在多个源文件中。在大系统中,在多个文件中实例化相同模板带来的开销可能会很严重。
//   通过显示实例化可以来避免上述开销。在一个cpp中定义,在其他所有使用此类实例的cpp中声明。 
extern template declaration;     //实例化声明
template declaration;            //实例化定义,其中declaration是一个类或者是函数声明,其中的模板参数均被模板实参替换。
//   由于编译器在使用一个模板时自动对其实例化,因此实例化声明必须出现在任何使用此实例化版本的代码之前。
//   一个类模板的显示实例化会实例化该模板的所有成员,包括内联的成员函数。当编译器遇见一个显示实例化定义的时候,它不了解程序使用哪些函数,因此与处理类模板的普通实例化不同,编译器会实例化该类的所有成员。

//15.如果一个函数形参的类型使用了模板类型参数,那么它将采用特殊的初始化规则。只有很有限的几种类型转换会自动应用于这些实参。编译器通常不是对实参进行类型转换而是生成一个新的模板实例。
//   能应用于函数模板的类型转换如下:
//     A:顶层const无论是在实参还是形参中都会被忽略。
//     B:const转换:一个const对象的引用或指针可以接受一个非const对象的引用或指针。
//     C:当函数形参不是引用类型的时候,则可以对数组或函数类型的实参应用正常的指针转换。
//   一个模板类型参数可以用作多个函数形参的类型,由于上述类型转换的限制,因此传递给这些形参的实参必须具有相同的类型。如果推断出来的类型不匹配,则调用就是错误的。
//   函数模板可以有普通类型定义的形参。这种函数形参能正常接收对应类型的实参(会进行正常的类型转换,而不需要遵守上述规则)。

//16.在某些情况下,编译器无法推断出模板参数的类型。则每次调用都必须为无法推断的那个形参提供一个显示模板实参。
template<typename T, typename X> T fun(X x)    {return T(0);}
int value = fun<int>(10);    //value = 0
//   对于模板类型参数已经显示指定了的函数形参,可以进行正常的类型转换
//   当我们希望由用户确定返回类型时,用显示模板实参表示模板函数的返回类型是很有效的,但是在其他情况下,要求显示指定模板实参会给用户带来负担,此时使用尾置返回类型可以有效解决这个问题。
template<typename T> auto fun(T beg)->decltype(*beg)        {return *beg;}//接受一个迭代器,返回迭代器所指对象的引用。

//17.标准库的类型转换模板。定义在头文件type_traits中,声明在命名空间std中。
对Mod<T>,其中Mod为:                若T为                    则Mod<T>::type为
remove_reference                  X&或    X&&              X
                                  否则                     T
add_const                         X&、const X 、函数        T
                                  否则                     const T
add_lvalue_reference              X&                       T
                                  X&&                      X&
                                  否则                      T&
add_rvalue_reference              X&、X&&                   T
                                  否则                       T&&
remove_pointer                    X*                        X
                                  否则                       T
add_pointer                       X&、X&&                   X*
                                  否则                       T*
make_signed                       unsigned X                X
                                  否则                       T
make_unsigned                     带符号类型                  unsigned X
                                  否则                       T
remove_extent                     X[n]                       X
                                  否则                       T
remove_all_extents                X[n1][n2]...               X
                                  否则                        T

//18.当函数参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,都能唯一确定其值或类型。

//19.引用折叠和右值引用参数:
//   右值引用参数:当我们将一个左值(如i, int类型)传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。此时编译器推断T的类型为int&,而非int。
//        T被推断为int&看起来好像意味着上述模板函数的参数应该是一个类型int&的右值引用。通常,我们不能直接定义一个引用的引用,但是通过类型别名或通过模板类型参数间接定义是可以的。
//   引用折叠:如果我们间接的创建了一个引用的引用,则这些引用将发生折叠。
//        X& &、X& && 、X&& &都折叠为X&
//        X&& &&折叠为X&&
//   注意点:引用折叠只能应用于间接创建的引用的引用,如:类型别名或模板参数。

//20.编写接受右值引用参数的模板函数:
template <typename T> void f3(T&& val)    {T t = val;}    //实际上f3的形参可以接受任意类型的实参
//   在上述函数中t到底是val的拷贝还是val的引用不能确定,应用remove_reference可能有点帮助,但是编写正确的代码仍然异常困难。
//   在实际编程中,右值引用通常应用于两种情况:模板转发其实参,模板被重载。
//   使用右值引用的函数模板通常的重载方式:
template <typename T> void f(T&&);         //绑定到非const右值
template <typename T> void f(const T&);    //绑定左值和const右值 
//   std::move()的定义:
template<class _Ty> inline typename tr1::_Remove_reference<_Ty>::_Type&& move(_Ty&& _Arg){return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);}
//   转发函数(要保持传入参数的const属性,以及实参是左值还是右值的)的一般定义:
template <typename F, typename T1, typename T2> void flip(F f, T1 &&t1, T2 &&t2){f(std::forward<T1>(t1), std::forward<T2>(t2)};
//   std::forward : 定义在头文件utility中,声明在命名空间std中,返回该显示实参类型的右值引用。即, forward<T>的返回类型为T&&。

//21.如果涉及函数模板,则重载函数的匹配规则会受到以下几方面的影响:
//   A:对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。
//   B:候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板。
//   C:与往常一样,可行函数按类型转换来排序。
//   D:与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。但是如果有多个函数提供同样好的匹配,则:
//        如果同样好的函数中只有一个是非模板函数,则选择此函数。
//        如果同样好的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他模板更特例化,则选择此模板。否则调用有歧义。

//22.模板特例化:编写单一模板,使之对任何可能的模板实参都是最合适的,都能实例化,这并不总是能办到的。在某些情况下,通用模板的定义对特定类型是不合适的:通用定义可能编译失败或者做的不正确。
//   其他时候,我们也可以利用某些特定的知识来编写更高效的代码,而不是从通用模板实例化。当我们不能使用模板版本时,可以定义类或者函数模板的一个特例化版本。

//23.为了指出我们正在实例化一个模板,应使用关键字template后跟一对空尖括号。空尖括号指出我们将为原模板的所有模板参数提供实参。
template <typename T>void Fun(T t){}
template<> void Fun(int t){}            //特例化版本
//   当我们定义一个特例化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配,否则会发生编译错误。
//   特例化的本质是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。
//   模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。
//   与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数。一个类模板的部分特例化本身是一个模板。
//   我们可以特例化类的成员而不是类。

//类模板部分特例化
template<typename T0, typename T1>
class CTest
{
};
template<typename T0>
class CTest<T0, int>
{
};

//特例化成员:
template<typename T0, typename T1>
class CTest
{
public:
    CTest(T0 t_0, T1 t_1) : t0(t_0), t1(t_1){}

public:
    T0 GetT0(){return t0;}
    T1 GetT1(){return t1;};

private:
    T0 t0;
    T1 t1;
};
template<> int CTest<int , int>::GetT1()    {return 10;}    //特例化成员

CTest<int , int> Test_Int(1, 2);
CTest<double, double> Test_Double(1.1, 2.2);
int value0 = Test_Int.GetT1();            //value0 = 10
double value1 = Test_Double.GetT1();    //value1 = 2.2000000000000002