泛型编程是C++继面向对象编程之后的又一个重点,是为了编写与具体类型无关的代码。而模板是泛型编程的基础。模板简单来理解,可以看作是用宏来实现的,事实上确实有人用宏来实现了模板类似的功能。模板,也可以理解为模具行业的模型。根据分类,有函数模板和类模板。根据传入的不同模板参数,函数模板会生成不同模板函数。类模板则生成不同的模板类。

 

模板参数

1.    概念

模板定义以关键字template开始,<>中是模板参数列表(template parameter list),模板参数列表即表示可以是一个或多个模板参数(template parameter)。

  • 模板实参则是在实例化函数模板或是类模板时的类型或值。
  •  模板形参是通过模板实参推导出来的或是直接显式指定。
  •  模板参数和普通函数参数一样,可以有默认值参数。

2.    模板形参分类

  • 类型参数(type parameter)

表明这个模板参数是一个类型。

template<class/typename T, ……>

  • 非类型参数(nontype parameter)

表明这个模板参数不是一个类型,而是常量数值,只能是整形、指针和引用。

复制代码
template<int N, ……> template<class/typename T, int N, ……> // template<const int* pM> void Test1(){}
template<const char M> void Test2()
{ int nArr[M] = {0};
} class A{};
template<A& AA> void Test3(){} const int n = 4; extern const int arr[] = {1, 2, 3};        
A a; int _tmain(int argc, _TCHAR* argv[])
{
Test1<arr>();

Test2<n>();

Test3<a>(); return 0;
}
复制代码

 

3.    模板实参

  • 模板实参推断(template argument deduction)

编译器使用函数调用中的实参类型来推断出模板实参,然后用这些实参生成对应的函数。

  • 显式模板实参(explicit template argument)

类模板生成具体的模板类,都是显式模板实参来实现的。

MyClass<int> myClass;
Set<int>(4);

有些模板函数没有实参,这时就必须通过显式模板实参来生成对应的模板函数。

模板函数有实参,但是类型可能无法正确推导,这时也可以加上显式模板实参的。

模板函数有实参,并且能够正确推导类型,这个时加与不加显式模板实参均可。

函数模板

1.    概念

函数模板,就是根据模板形参的不同,生成不同的模板函数。

template<class/typename T, ……> retType FunctionName(parameter)
{ // Function Body }

 

class和typename在此处意义一样,只是用class容易被人误解为后面的形参T是类。

函数模板在没有实例化时,只是一个生成函数的模板。

而在具体的实例化之后,即指明具体的形参,就生成了一个具体的模板函数。

2.    声明和定义

函数模板与普通函数一样,声明与定义可以分开,也可以一起。

  • 声明
  template<typename T> bool Compare(T t1, T t2);
  •  定义
template<typename T> bool Compare(T t1, T t2)
{ return (t1 > t2);
}

类模板

1.    概念

类模板根据模板形参的不同,生成不同的模板类。

template<class/typename T, ……> class ClassName
{ // Class definition };

类模板,在没有实例化之前,只是一个生成类的模板。

而在具体的实例化之后,其实就生成了一个具体的模板类。

2.    声明与定义

  • 声明
复制代码
template<class/typename T> class ClassName
{ // Class Declaration void Test();
};
复制代码
  • 定义

直接在类模板内部定义成员函数,也可以在模板类的外部定义成员函数。

template<class/typename T> void ClassName<T>::Test()
{
}
  • 前置声明

和普通的函数声明一样,参数指明与不指明均可。

template<class/typename T> class ClassName;
template<class/typename> class ClassName;

             类的类型成员

1.     概念

T::size_type *p;

编译器无法识别上面是定义一个指针变量p,还是一个T中的一个静态成员变量size_type与变量p相乘。

所以引入了typename来标识后面T::后面的类型,而不是变量。注意此处只能用typename,不能用class。

复制代码
struct NEW_TYPE
{ public:
typedef int n_size;
};

template<typename T> typename T::n_size Set(typename T::n_size _n)
{
  typename T::n_size m = 2;
  T::n_size n = 3;
  T::n_size* p; return n;
}
复制代码

VS编译器不能识别T::n_size是一个静态常量还是一个类型,所以这个时候需要用typename来标识它是一个类型。VS编译器能够识别T::n_size* p;经测试返回类型以及形参类型时必须用typename指定其为类型。

模板编译与链接

1.    编译

C++是采用声明和实现分在两个文件中,因为这样可以使用分离编译。编译每个cpp文件,如果遇上外部函数只需要记录其名字即可。模板代码,一般放在hpp文件中,即声明和实现代码在一起。因为C++是编译型语言而不是解释型语言,所以模型的具体实现代码编译时必须确定下来。模板在某种程度上,类似于宏,甚至有人用宏实现类似模板的功能。很多时候一个类模板会包括很多功能,为了防止代码膨胀,编译模板时编译器会自动识别用到的函数,用到的类,也就是只编译用到的。也就是我们模板中,有一些函数如果没有用一以,哪怕其中有语法错误,也不会被编译到,自然也就不会报错了。

那么在多个CPP文件中用到了相同形参的函数模板,因为CPP是分离编译的,所以每个CPP文件中都会编译出一个相同的模板函数

2.    链接

每个Cpp编译之后,生成一个obj的目标文件,然后链接器链接这些目标文件生成DLL或exe。链接的过程中,链接器会检查是否有重复定义,抑或库冲突之类的。链接的时候,链接器会使用前面已经生成的模板函数,自动放弃后面生成的模板函数,这样能够防止重复定义以及可能避免代码膨胀导致的exe增大。这种方式导致的一个坏处是,大幅增加编译时间,因为用到的模板都会实例化并进行编译。

模板能不能使用分离编译呢?网上基本上都说不能,理由是模板实现代码放在CPP中时,并不知道具体的模板参数类型,所以无法编译。既然无法知道模板的具体形数,那么就解决这个问题,告诉编译器具体的形参。

显式实例化

1.    概念

显式实例化(explicit instantiation),就是显式地告诉编译器模板形参的类型或值。

复制代码
extern template declaration; // 外部实例化声明 template declaration; // 实例化定义 extern template void Test<int>(const int& _t);
template void Test<int>(const int& _t); extern template class Ctest<char*>;
template class Ctest<char*>;
复制代码

extern在修饰模板声明时的作用与声明全局变量的作用一样,就是告诉编译器,当前修饰的声明已经在其他CPP文件中定义。

实例化定义,一种是直接显式实例化,另一种是隐式实例化即编译器识别到CPP中有模板的实例化代码也同样会实例化。

外部实例化声明是为了解决重复实例化,提升编译效率。但是习惯了声明和定义分开的编程习惯。我们同样可以用显式实例化来分离编译。函数模板和类模板的显式实例化方法一样的。

2.    实例

h文件只用来存放模板声明

复制代码
// fun.h file template<typename T> void Test(const T& _t);
// cpp文件存放定义及具体的实例化定义,即显式实例化。这样实例化肯定只有一次,并且结构清晰。这种方式适用于模板实例化的具体类型不多的情况。因为如果模板是库文件,不停修改是不方便的。 // fun.cpp file template void Test<int>(const int& _t);
template void Test<float>(const float& _t);

template<typename T> void Test(const T& _t)
{
    T t = _t;
}
复制代码

尾置返回类型

1.    概念

尾置返回类型(trailing return type)是在形参列表后面以->符号开始标明函数的返回类型,并在函数返回类型处用auto代替。尾置返回类型即可以直接指明类型,也可以用decltype推出出类型

2.    实例

复制代码
auto Function(int i)->int auto Fun3(int i)->int(*)[5] // 返回指定数组的指针 int n = 10;
auto Function(int i)->decltype(n)
template<class T, class W> auto Function(T t, W w)->decltype(t+w)
{ return t +w;

}
// 如果是自定义类型,则应该重载+实现t+w
复制代码

3.    备注

注:C++14中,已经将尾置返回类型去掉了,可以直接用auto推导出类型。

参考:msdn.microsoft.com/en-us/library/dd537655(v=vs.100).aspx

函数模板指针

1.    普通函数指针

因为C++要兼容C,所以函数名加&与不加都表示函数指针。

  • 实例化的模板函数的指针
复制代码
template<typename T> void Test(const T& _t)
{
    T t = _t;
}

typedef void (*pTest)(const int& _t);
pTest p = Test<int>;
pTest pT = &Test<float>;
p(3);
复制代码
  • 用模板参数来指代函数指针
复制代码
template <typename T, typename NAME_TYPE> void TestFun(T fun, NAME_TYPE n)
{
    fun(n);
}

TestFun(Test<int>, 4);
TestFun(&Test<double>, 4.0); // 用&与否均可
复制代码

2.    类成员函数指针

  • 类静态成员函数指针

和普通函数指针一样,都是__cdecl的调用方式,只能直接调用。

CMyClass<int>::pFunCalc pCalc = CMyClass<int>::Calc;

pCalc();

  •  类成员函数指针

成员函数指针声明时必须是&ClassName::,这是自VS2005之后就必须要求,以前类名加与不加&均可。其实不加&不规范,因为函数名并不是指针。函数名是对象,取地址是才是指针。自VS2005之后,类函数名指针必须加&。并且类函数指针调用时,必须指明调用对象标明是__thiscall的调用方式(这是类函数特有的调用方式,因为它会将类指针作为参数传递进去)。

复制代码
CMyClass<int> myClass;
CMyClass<int>::pFunSetValue pSet = &CMyClass<int>::SetValue;
myClass.SetValue(4);
(myClass.*(&CMyClass<int>::SetValue))(4);
(myClass.*pSet)(4);
myClass.TestFun(pSet, 4);
myClass.TestFun(&CMyClass<int>::SetValue, 4); // 模板代码 template<typename T> class CMyClass
{ public: // 普通函数的参数传递方式,默认可以不加__cdecl typedef int (__cdecl *pFunCalc)(); // 标识这是一种__thiscall的参数传递方式,类成员函数特有的 typedef void (CMyClass::*pFunSetValue)(const T& _t); public: void TestFun(pFunSetValue _pFun, const T& _t)
    { // 成员函数指针必须指明调用对象标明是__thiscall的调用方式 (this->*_pFun)(_t);
       (this->*(&CMyClass<T>::SetValue))(4); // 静态函数和普通一样,不用指明调用对象,表明__cdecl的调用方式  (*(&CMyClass<T>::Calc))();                        
} void SetValue(const T& _t)
    {
        m_t = _t;
    } static int Calc()
    {
        T t = 2; return t*2;
    } private:
    T m_t;
}; // 在另外的类中使用成员函数指针 template<typename T> class CMyTest
{ public:
     typedef void (CMyClass <T>::*pFunSet)(const T& t); void TestFun(MyClass<T>* p, pFunSet fun, T t)
     {
              (p->*fun)(t);
     } void Test(MyClass<T>* p, typename CMyClass <T>:: pFunSetValue fun, T t)
     {
              (p->*fun)(t);
     }

};
复制代码

模板特化与偏特化

1.    概念

  • 模板的特化

即模板的特殊化,即模板的通胀算法不能满足特殊实例。那么即需要单独的代码来处理特殊的实例。而实例是根据不同的形参类型决定的。特化就是处理模板的某一特殊模板形参。形式:

复制代码
template<typename T1, typename T2> class Test{};
template<typename T1, typename T2> void Set(T1 t1, T2 t2){} // specialization template<> class Test<int, int>{};
template<> void Set(int t1, int t2){} // call the special function Test<int, int> test;
Set(4, 3);
Set<int, int>(4, 3);
复制代码
  • 模板的偏特化

模板的偏特化只能用于类模板,不能用于函数模板,函数模板只有重载。

复制代码
template<typename T1, typename T2> class Test{};
template<typename T1, int N> class TestNon{}; // partial specialization template<typename T1> class Test<T1, int>{};
template<typename T1> class TestNon<T1, 5>{};
Test<char*, int> test;
TestNon<int, 5> testNon;
复制代码

2.    应用

因为特化和偏特化均是在编译时实现的。所以我们能够将一些逻辑判断移到编译期来做。这样能够提前测试代码,因为编译期发现错误比运行过程中容易。模板元编程就是这样实现的。另外我们可以用特化来处理一些异常。将异常情况移到特化代码中处理,这样主代码的逻辑就会更简单清晰。

  • 类型模板形参
复制代码
// Boost中一个例子。 template< typename T > struct is_pointer
{ static const bool value = false;
 };

 template< typename T > struct is_pointer< T* > { static const bool value = true;
 };
复制代码

这样我就可以通过is_pointer<T>::value来判断当前类型是否为指针类型。

  • 非类型模板形参
复制代码
Template<bool b> Struct algo_sort
{
  Template<typename T> Static void sort(T& obj)
  {
    Quick_sort(obj);
  }
}

Template<> Struct algo_sort<true> {
  Template<typename T> Static void sort(T& obj)
  {
    Select_sort(obj);
  }
}
复制代码

这样就能够通过模板形参的不同调用不同的排序方法。

 

仿函数

 

1.    概念:

仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。


复制代码
// alg_for_each.cpp // compile with: /EHsc #include <vector> #include <algorithm> #include <iostream> // The function object multiplies an element by a Factor template <class Type> class MultValue
{ private:
   Type Factor; // The value to multiply by public: // Constructor initializes the value to multiply by MultValue ( const Type& _Val ) : Factor ( _Val )
     {
   } // The function call for the element to be multiplied void operator ( ) ( Type& elem ) const {
      elem *= Factor;
   }
}; int main( )
{
   std::vector <int> v1;
   std::vector <int>::iterator Iter1; // Constructing vector v1 for ( int i = -4 ; i <= 2 ; i++ )
   {
      v1.push_back(  i );
   } // Using for_each to multiply each element by a Factor std::for_each ( v1.begin ( ) , v1.end ( ) , MultValue<int> ( -2 ) );

}
复制代码

2.    解析

上面标注为红色的代码,因为类MultValue重载了括号运算符,光看代码,很容易将这里理解成了括号运算。但这是错误的理解。首先我们回到for_each这个函数本身的理解上来,MSDN对第三个参数给出的解释:User-defined function object that is applied to each element in the range. 可以知道,第三个参数是一个函数对象。

 这里就需要我们有这样一个概念,如果一个类重载了括号运算符,那么这个类建立的对象就具有类似函数的功能。这样的话,那么第三个参数就可以是一个重载了括号的对象了。

MultiValue<int> & mValue =  MultValue<int> ( -2 );
for_each ( v1.begin ( ) , v1.end ( ) , mValue); // 1  for_each ( v1.begin ( ) , v1.end ( ) , MultValue<int> ( -2 ) ); // 2

直接用上面这种方式来写会好理解多了。再来理解MultValue<int> (-2)这样一句代码,它直接生成了一个无名的临时对象,然后初始化一个引用。MultValue<int> (-2);这样的一句话,将直接调用构造函数生成一个无名的临时对象。这样看来,上面的语句1和语句2其实是一个意思。

for_each函数的第三个函数本来可以直接用一个用户定义的函数来完成,但是为什么MSDN中多用重载类的括号运算符来完成这样的功能呢?通过上面的例子,我们可以发现,主要表现类的构造函数上,可以初始化不同的参数,这一点是自定义的函数所不具备的。另外,利用结构体还能把可能用到的函数封装到一个结构体中,便于管理。

   这样回头看以前常用的sort函数。

复制代码
vector<int> vInt;
sort(vInt.begin(), vInt.end(), greater<int> ());
sort(vInt.begin(), vInt.end(), less<int> ()); // STL中的less<int>的代码. // TEMPLATE STRUCT less template<class _Ty> struct less
: public binary_function<_Ty, _Ty, bool> { // functor for operator< bool operator()(const _Ty& _Left, const _Ty& _Right) const { // apply operator< to operands return (_Left < _Right);
  }
}; // 在C++中struct基本上和class一个意思
复制代码

greater<int>()和less<int>()也是直接构建一个无名对象,然后调用重载的括号运算符。

其他

  • 函数模板可以重载,和普通函数重载类似,函数模板重载某些时候能也达到特化的效果。

template<typename M, typename N>  void TestSpec(M m, N n){}

template<typename M>  void TestSpec(M m, int n){}

void TestSpec(int m ,int n){}

  • 类模板中,还可以添加成员模板函数。
  • 友元模板类需要前到前置声明。
  • 派生类只能从模板类派生(类模板的一个实例化)。

泛型编程是C++继面向对象编程之后的又一个重点,是为了编写与具体类型无关的代码。而模板是泛型编程的基础。模板简单来理解,可以看作是用宏来实现的,事实上确实有人用宏来实现了模板类似的功能。模板,也可以理解为模具行业的模型。根据分类,有函数模板和类模板。根据传入的不同模板参数,函数模板会生成不同模板函数。类模板则生成不同的模板类。

 

模板参数

1.    概念

模板定义以关键字template开始,<>中是模板参数列表(template parameter list),模板参数列表即表示可以是一个或多个模板参数(template parameter)。

  • 模板实参则是在实例化函数模板或是类模板时的类型或值。
  •  模板形参是通过模板实参推导出来的或是直接显式指定。
  •  模板参数和普通函数参数一样,可以有默认值参数。

2.    模板形参分类

  • 类型参数(type parameter)

表明这个模板参数是一个类型。

template<class/typename T, ……>

  • 非类型参数(nontype parameter)

表明这个模板参数不是一个类型,而是常量数值,只能是整形、指针和引用。

复制代码
template<int N, ……> template<class/typename T, int N, ……> // template<const int* pM> void Test1(){}
template<const char M> void Test2()
{ int nArr[M] = {0};
} class A{};
template<A& AA> void Test3(){} const int n = 4; extern const int arr[] = {1, 2, 3};        
A a; int _tmain(int argc, _TCHAR* argv[])
{
Test1<arr>();

Test2<n>();

Test3<a>(); return 0;
}
复制代码

 

3.    模板实参

  • 模板实参推断(template argument deduction)

编译器使用函数调用中的实参类型来推断出模板实参,然后用这些实参生成对应的函数。

  • 显式模板实参(explicit template argument)

类模板生成具体的模板类,都是显式模板实参来实现的。

MyClass<int> myClass;
Set<int>(4);

有些模板函数没有实参,这时就必须通过显式模板实参来生成对应的模板函数。

模板函数有实参,但是类型可能无法正确推导,这时也可以加上显式模板实参的。

模板函数有实参,并且能够正确推导类型,这个时加与不加显式模板实参均可。

函数模板

1.    概念

函数模板,就是根据模板形参的不同,生成不同的模板函数。

template<class/typename T, ……> retType FunctionName(parameter)
{ // Function Body }

 

class和typename在此处意义一样,只是用class容易被人误解为后面的形参T是类。

函数模板在没有实例化时,只是一个生成函数的模板。

而在具体的实例化之后,即指明具体的形参,就生成了一个具体的模板函数。

2.    声明和定义

函数模板与普通函数一样,声明与定义可以分开,也可以一起。

  • 声明
  template<typename T> bool Compare(T t1, T t2);
  •  定义
template<typename T> bool Compare(T t1, T t2)
{ return (t1 > t2);
}

类模板

1.    概念

类模板根据模板形参的不同,生成不同的模板类。

template<class/typename T, ……> class ClassName
{ // Class definition };

类模板,在没有实例化之前,只是一个生成类的模板。

而在具体的实例化之后,其实就生成了一个具体的模板类。

2.    声明与定义

  • 声明
复制代码
template<class/typename T> class ClassName
{ // Class Declaration void Test();
};
复制代码
  • 定义

直接在类模板内部定义成员函数,也可以在模板类的外部定义成员函数。

template<class/typename T> void ClassName<T>::Test()
{
}
  • 前置声明

和普通的函数声明一样,参数指明与不指明均可。

template<class/typename T> class ClassName;
template<class/typename> class ClassName;

             类的类型成员

1.     概念

T::size_type *p;

编译器无法识别上面是定义一个指针变量p,还是一个T中的一个静态成员变量size_type与变量p相乘。

所以引入了typename来标识后面T::后面的类型,而不是变量。注意此处只能用typename,不能用class。

复制代码
struct NEW_TYPE
{ public:
typedef int n_size;
};

template<typename T> typename T::n_size Set(typename T::n_size _n)
{
  typename T::n_size m = 2;
  T::n_size n = 3;
  T::n_size* p; return n;
}
复制代码

VS编译器不能识别T::n_size是一个静态常量还是一个类型,所以这个时候需要用typename来标识它是一个类型。VS编译器能够识别T::n_size* p;经测试返回类型以及形参类型时必须用typename指定其为类型。

模板编译与链接

1.    编译

C++是采用声明和实现分在两个文件中,因为这样可以使用分离编译。编译每个cpp文件,如果遇上外部函数只需要记录其名字即可。模板代码,一般放在hpp文件中,即声明和实现代码在一起。因为C++是编译型语言而不是解释型语言,所以模型的具体实现代码编译时必须确定下来。模板在某种程度上,类似于宏,甚至有人用宏实现类似模板的功能。很多时候一个类模板会包括很多功能,为了防止代码膨胀,编译模板时编译器会自动识别用到的函数,用到的类,也就是只编译用到的。也就是我们模板中,有一些函数如果没有用一以,哪怕其中有语法错误,也不会被编译到,自然也就不会报错了。

那么在多个CPP文件中用到了相同形参的函数模板,因为CPP是分离编译的,所以每个CPP文件中都会编译出一个相同的模板函数

2.    链接

每个Cpp编译之后,生成一个obj的目标文件,然后链接器链接这些目标文件生成DLL或exe。链接的过程中,链接器会检查是否有重复定义,抑或库冲突之类的。链接的时候,链接器会使用前面已经生成的模板函数,自动放弃后面生成的模板函数,这样能够防止重复定义以及可能避免代码膨胀导致的exe增大。这种方式导致的一个坏处是,大幅增加编译时间,因为用到的模板都会实例化并进行编译。

模板能不能使用分离编译呢?网上基本上都说不能,理由是模板实现代码放在CPP中时,并不知道具体的模板参数类型,所以无法编译。既然无法知道模板的具体形数,那么就解决这个问题,告诉编译器具体的形参。

显式实例化

1.    概念

显式实例化(explicit instantiation),就是显式地告诉编译器模板形参的类型或值。

复制代码
extern template declaration; // 外部实例化声明 template declaration; // 实例化定义 extern template void Test<int>(const int& _t);
template void Test<int>(const int& _t); extern template class Ctest<char*>;
template class Ctest<char*>;
复制代码

extern在修饰模板声明时的作用与声明全局变量的作用一样,就是告诉编译器,当前修饰的声明已经在其他CPP文件中定义。

实例化定义,一种是直接显式实例化,另一种是隐式实例化即编译器识别到CPP中有模板的实例化代码也同样会实例化。

外部实例化声明是为了解决重复实例化,提升编译效率。但是习惯了声明和定义分开的编程习惯。我们同样可以用显式实例化来分离编译。函数模板和类模板的显式实例化方法一样的。

2.    实例

h文件只用来存放模板声明

复制代码
// fun.h file template<typename T> void Test(const T& _t);
// cpp文件存放定义及具体的实例化定义,即显式实例化。这样实例化肯定只有一次,并且结构清晰。这种方式适用于模板实例化的具体类型不多的情况。因为如果模板是库文件,不停修改是不方便的。 // fun.cpp file template void Test<int>(const int& _t);
template void Test<float>(const float& _t);

template<typename T> void Test(const T& _t)
{
    T t = _t;
}
复制代码

尾置返回类型

1.    概念

尾置返回类型(trailing return type)是在形参列表后面以->符号开始标明函数的返回类型,并在函数返回类型处用auto代替。尾置返回类型即可以直接指明类型,也可以用decltype推出出类型

2.    实例

复制代码
auto Function(int i)->int auto Fun3(int i)->int(*)[5] // 返回指定数组的指针 int n = 10;
auto Function(int i)->decltype(n)
template<class T, class W> auto Function(T t, W w)->decltype(t+w)
{ return t +w;

}
// 如果是自定义类型,则应该重载+实现t+w
复制代码

3.    备注

注:C++14中,已经将尾置返回类型去掉了,可以直接用auto推导出类型。

参考:msdn.microsoft.com/en-us/library/dd537655(v=vs.100).aspx

函数模板指针

1.    普通函数指针

因为C++要兼容C,所以函数名加&与不加都表示函数指针。

  • 实例化的模板函数的指针
复制代码
template<typename T> void Test(const T& _t)
{
    T t = _t;
}

typedef void (*pTest)(const int& _t);
pTest p = Test<int>;
pTest pT = &Test<float>;
p(3);
复制代码
  • 用模板参数来指代函数指针
复制代码
template <typename T, typename NAME_TYPE> void TestFun(T fun, NAME_TYPE n)
{
    fun(n);
}

TestFun(Test<int>, 4);
TestFun(&Test<double>, 4.0); // 用&与否均可
复制代码

2.    类成员函数指针

  • 类静态成员函数指针

和普通函数指针一样,都是__cdecl的调用方式,只能直接调用。

CMyClass<int>::pFunCalc pCalc = CMyClass<int>::Calc;

pCalc();

  •  类成员函数指针

成员函数指针声明时必须是&ClassName::,这是自VS2005之后就必须要求,以前类名加与不加&均可。其实不加&不规范,因为函数名并不是指针。函数名是对象,取地址是才是指针。自VS2005之后,类函数名指针必须加&。并且类函数指针调用时,必须指明调用对象标明是__thiscall的调用方式(这是类函数特有的调用方式,因为它会将类指针作为参数传递进去)。

复制代码
CMyClass<int> myClass;
CMyClass<int>::pFunSetValue pSet = &CMyClass<int>::SetValue;
myClass.SetValue(4);
(myClass.*(&CMyClass<int>::SetValue))(4);
(myClass.*pSet)(4);
myClass.TestFun(pSet, 4);
myClass.TestFun(&CMyClass<int>::SetValue, 4); // 模板代码 template<typename T> class CMyClass
{ public: // 普通函数的参数传递方式,默认可以不加__cdecl typedef int (__cdecl *pFunCalc)(); // 标识这是一种__thiscall的参数传递方式,类成员函数特有的 typedef void (CMyClass::*pFunSetValue)(const T& _t); public: void TestFun(pFunSetValue _pFun, const T& _t)
    { // 成员函数指针必须指明调用对象标明是__thiscall的调用方式 (this->*_pFun)(_t);
       (this->*(&CMyClass<T>::SetValue))(4); // 静态函数和普通一样,不用指明调用对象,表明__cdecl的调用方式  (*(&CMyClass<T>::Calc))();                        
} void SetValue(const T& _t)
    {
        m_t = _t;
    } static int Calc()
    {
        T t = 2; return t*2;
    } private:
    T m_t;
}; // 在另外的类中使用成员函数指针 template<typename T> class CMyTest
{ public:
     typedef void (CMyClass <T>::*pFunSet)(const T& t); void TestFun(MyClass<T>* p, pFunSet fun, T t)
     {
              (p->*fun)(t);
     } void Test(MyClass<T>* p, typename CMyClass <T>:: pFunSetValue fun, T t)
     {
              (p->*fun)(t);
     }

};
复制代码

模板特化与偏特化

1.    概念

  • 模板的特化

即模板的特殊化,即模板的通胀算法不能满足特殊实例。那么即需要单独的代码来处理特殊的实例。而实例是根据不同的形参类型决定的。特化就是处理模板的某一特殊模板形参。形式:

复制代码
template<typename T1, typename T2> class Test{};
template<typename T1, typename T2> void Set(T1 t1, T2 t2){} // specialization template<> class Test<int, int>{};
template<> void Set(int t1, int t2){} // call the special function Test<int, int> test;
Set(4, 3);
Set<int, int>(4, 3);
复制代码
  • 模板的偏特化

模板的偏特化只能用于类模板,不能用于函数模板,函数模板只有重载。

复制代码
template<typename T1, typename T2> class Test{};
template<typename T1, int N> class TestNon{}; // partial specialization template<typename T1> class Test<T1, int>{};
template<typename T1> class TestNon<T1, 5>{};
Test<char*, int> test;
TestNon<int, 5> testNon;
复制代码

2.    应用

因为特化和偏特化均是在编译时实现的。所以我们能够将一些逻辑判断移到编译期来做。这样能够提前测试代码,因为编译期发现错误比运行过程中容易。模板元编程就是这样实现的。另外我们可以用特化来处理一些异常。将异常情况移到特化代码中处理,这样主代码的逻辑就会更简单清晰。

  • 类型模板形参
复制代码
// Boost中一个例子。 template< typename T > struct is_pointer
{ static const bool value = false;
 };

 template< typename T > struct is_pointer< T* > { static const bool value = true;
 };
复制代码

这样我就可以通过is_pointer<T>::value来判断当前类型是否为指针类型。

  • 非类型模板形参
复制代码
Template<bool b> Struct algo_sort
{
  Template<typename T> Static void sort(T& obj)
  {
    Quick_sort(obj);
  }
}

Template<> Struct algo_sort<true> {
  Template<typename T> Static void sort(T& obj)
  {
    Select_sort(obj);
  }
}
复制代码

这样就能够通过模板形参的不同调用不同的排序方法。

 

仿函数

 

1.    概念:

仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。


复制代码
// alg_for_each.cpp // compile with: /EHsc #include <vector> #include <algorithm> #include <iostream> // The function object multiplies an element by a Factor template <class Type> class MultValue
{ private:
   Type Factor; // The value to multiply by public: // Constructor initializes the value to multiply by MultValue ( const Type& _Val ) : Factor ( _Val )
     {
   } // The function call for the element to be multiplied void operator ( ) ( Type& elem ) const {
      elem *= Factor;
   }
}; int main( )
{
   std::vector <int> v1;
   std::vector <int>::iterator Iter1; // Constructing vector v1 for ( int i = -4 ; i <= 2 ; i++ )
   {
      v1.push_back(  i );
   } // Using for_each to multiply each element by a Factor std::for_each ( v1.begin ( ) , v1.end ( ) , MultValue<int> ( -2 ) );

}
复制代码

2.    解析

上面标注为红色的代码,因为类MultValue重载了括号运算符,光看代码,很容易将这里理解成了括号运算。但这是错误的理解。首先我们回到for_each这个函数本身的理解上来,MSDN对第三个参数给出的解释:User-defined function object that is applied to each element in the range. 可以知道,第三个参数是一个函数对象。

 这里就需要我们有这样一个概念,如果一个类重载了括号运算符,那么这个类建立的对象就具有类似函数的功能。这样的话,那么第三个参数就可以是一个重载了括号的对象了。

MultiValue<int> & mValue =  MultValue<int> ( -2 );
for_each ( v1.begin ( ) , v1.end ( ) , mValue); // 1  for_each ( v1.begin ( ) , v1.end ( ) , MultValue<int> ( -2 ) ); // 2

直接用上面这种方式来写会好理解多了。再来理解MultValue<int> (-2)这样一句代码,它直接生成了一个无名的临时对象,然后初始化一个引用。MultValue<int> (-2);这样的一句话,将直接调用构造函数生成一个无名的临时对象。这样看来,上面的语句1和语句2其实是一个意思。

for_each函数的第三个函数本来可以直接用一个用户定义的函数来完成,但是为什么MSDN中多用重载类的括号运算符来完成这样的功能呢?通过上面的例子,我们可以发现,主要表现类的构造函数上,可以初始化不同的参数,这一点是自定义的函数所不具备的。另外,利用结构体还能把可能用到的函数封装到一个结构体中,便于管理。

   这样回头看以前常用的sort函数。

复制代码
vector<int> vInt;
sort(vInt.begin(), vInt.end(), greater<int> ());
sort(vInt.begin(), vInt.end(), less<int> ()); // STL中的less<int>的代码. // TEMPLATE STRUCT less template<class _Ty> struct less
: public binary_function<_Ty, _Ty, bool> { // functor for operator< bool operator()(const _Ty& _Left, const _Ty& _Right) const { // apply operator< to operands return (_Left < _Right);
  }
}; // 在C++中struct基本上和class一个意思
复制代码

greater<int>()和less<int>()也是直接构建一个无名对象,然后调用重载的括号运算符。

其他

  • 函数模板可以重载,和普通函数重载类似,函数模板重载某些时候能也达到特化的效果。

template<typename M, typename N>  void TestSpec(M m, N n){}

template<typename M>  void TestSpec(M m, int n){}

void TestSpec(int m ,int n){}

  • 类模板中,还可以添加成员模板函数。
  • 友元模板类需要前到前置声明。
  • 派生类只能从模板类派生(类模板的一个实例化)。