和面向对象编程的显示接口和运行期多态不同,
泛型编程更多是隐式接口和编译期多态。
#includeusing namespace std; class A { public: A() { x = 12; cout << "Create A" << endl; } virtual std::size_t size() const { return x; } virtual void Init() { x = 10; cout << "Init A" << endl; } void swap(A& other) { int t = x; x = other.x; other.x = t; cout << "Swap" << endl; } bool operator != (const int & x) { cout << "!=" << endl; return this->x != x; } private: int x; }; template void Func(T& a) { if (a.size() > 10 && a != 5) { T temp(a); temp.Init(); temp.swap(a); } } int main() { A a; Func(a); return 0; }
1.以不同的参数具现化会导致调用不同的函数,这便是所谓的编译期多态。
2.泛型编程必须支持对应的隐式转换,如果函数中使用到诸如,size(),Init(),swap(),!=
请记住:
classes和templates都支持接口和多态。
对classes而言接口是显示的,以签名函数为中心。多态则是virtual函数发生于运行期。
对template参数而言接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。
templatetemplate
没什么不同,但是c++并不总是把class和typename视为等价。
涉及嵌套从属名称时只能使用关键字typename:
(从属名称(dependent name):template内出现的名称(示例中的iter)如果相依于某个template参数(示例中的C)的名称。如果从属名称在class内呈嵌套状,则称为嵌套从属名称(nested dependent name)。const_iterator也是类。
)
#includeusing namespace std; class A { public: typedef int const_iterator; private: int x; }; template void Func(T& x) { T::const_iterator* x; } int main() { A a; Func(a); return 0; }
这段代码编译会有报错
为什么?
因为我们不知道T::const_iterator是不是一个类型。我们无法确定!
可能T的某个静态成员变量恰好叫const_iterator,或者x碰巧是个global变量名称呢?
那样的话上述代码甚至不再是声明一个local变量而是一个相动作!!!
(T::const_iterator)* (x)!!!
所以c++规定必须使用typename为前缀。
#includeusing namespace std; class A { public: typedef int const_iterator; private: int x; }; template void Func(T& a) { typename T::const_iterator* x; } int main() { A a; Func(a); return 0; }
任何时候当你想要在template中指涉一个从属类型名称,就必须在紧临它的前一个位置放上关键字typename。
但是有例外!typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值列)中作为base class 修饰符。
#includeusing namespace std; template class Base { public: class Nested { public: Nested(int x) { cout << x << endl; } }; }; template class Dierived :public Base ::Nested { public: Dierived(int x) :Base ::Nested(x) {} int x; }; int main() { Dierived d(10); return 0; }
即不能写成
typename Base::Nested
对于这种长名称我们可以通过typedef简写,这是非常常用的技巧,谁不想少写点单词呢?
templatevoid Func(T& a) { typedef typename T::const_iterator* iter; iter x; }
请记住:
声明template参数时,前缀关键字class和tyoename可互换
请使用typename标识嵌套从属类型名称:但不得在base class lists(基类列)和member initalization list(成员初值列)内以它作为base class修饰符
看这一段代码。Send(info)无法通过编译
#includeusing namespace std; class CompanyA { public: void Send(string msg) { cout << "A Send : " << msg << endl; } }; class CompanyB { void Send(string msg) { cout << "B Send : " << msg << endl; } }; class MsgInfo { public: string msg; }; template class MsgSender { void Send(const MsgInfo& info) { string msg; msg = info.msg; Company c; c.Send(msg); } }; template class LoggingMsgSender :public MsgSender { public: void SendLog(const MsgInfo& info) { //log Send(info);//编译器不知道Company是什么,所以无法知道MsgSender 是什么 } }; int main() { return 0; }
问题在于,当编译器遭遇class template LoggingMsgSender定义式时,并不知道他继承的是什么样的class。主要是不知道Company是什么。更明确的说是无法确定有Send这个函数。
这涉及到模板特化的问题!
我们看一个例子,假设我们引入了一个模板全特化的MsgSender
class CompanyZ
{
};
template<>
//一个全特化的MsgSender
class MsgSender
//,它和一般template相同,差别只是没有Send
{
};
所谓的模板全特化:template MsgSender针对类型Companyz特化了,并且其特化是全面的,也就是说一旦类型参数被定义为Companyz,再没有其他template参数可供变化。
(说人话:模板参数列表中all的模板参数都用具体的类型标识)
当类模板/函数模板进行全特化之后,这个全特化后的类/函数就不是一个模板类/函数了
(因为全特化完成后,模板参数类型为空了,即template<>了,就是一个具体的类/函数)
那么我们再考虑之前的LoggingMsgSender
templateclass LoggingMsgSender :public MsgSender { public: void SendLog(const MsgInfo& info) { //log Send(info);//如果Company==Companyz这个函数不存在 } };
这就是为什么C++拒绝调用的原因:它知道base class template可能被特化,而那个特化版本可能不提供和一般性template相同的接口。
解决方案有三种:
1.加上“this->”
templateclass LoggingMsgSender :public MsgSender { public: void SendLog(const MsgInfo& info) { //log this->Send(info); } };
2.使用using声明式
templateclass LoggingMsgSender :public MsgSender { public: using MsgSender ::Send; void SendLog(const MsgInfo& info) { //log Send(info); } };
3.明白指出被调用的函数位于base class内:
templateclass LoggingMsgSender :public MsgSender { public: void SendLog(const MsgInfo& info) { //log MsgSender ::Send(info); } };
请记住:
可在derived class templates内通过”this->”指涉base class templates内的成员名称,或藉由一个明白写出的“base class 资格修饰符”完成。
使用template可能导致代码膨胀,二进制码会带着重复(或者几乎重复)的代码、数据,或两者。其结果有可能源码看起来合身而整齐,但目标码却不是这么回事。所以该条例用于我们解决template带来的代码膨胀问题。
参考这一篇
#includeusing namespace std; template class SquareMatrixBase { public: SquareMatrixBase(T* p) : DataPointer(p) {} void Invert(size_t n) {}//求逆矩阵 private: T* DataPointer; }; template class SquareMatrix:private SquareMatrixBase //n*n的矩阵T类型的 { public: SquareMatrix() : SquareMatrixBase (Data) {} void Invert() { SquareMatrixBase ::Invert(n);//调父类函数 } private: T Data[n * n]; }; int main() { SquareMatrix a; SquareMatrix b; }
请记住:
Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。
真实的指针考研支持隐式转换。但我们自己简单写的指针不能这样
#includeusing namespace std; template class SmartPtr { public: SmartPtr(T* p) : p(p) {} private: T* p; }; class A { }; class B :public A { }; int main() { SmartPtr p1=SmartPtr(new B);//不行 shared_ptr p2 = shared_ptr(new B);//可以 }
为什么?如果以带有base-derived关系的A和B类分别具现化某个template,产生出来的两个具现化并不带有base-derived的关系,所以编译器认为这是两个完全无关的类。
解决方案是生成一个member template copy构造函数
#includeusing namespace std; template class SmartPtr { public: template SmartPtr(const SmartPtr & other):p(static_cast (other.get())) { } SmartPtr(T* p) : p(p) {} T* get() const { return p; } private: T* p; }; class A { }; class B :public A { }; int main() { SmartPtr p1=SmartPtr(new B);//不行 shared_ptr p2 = shared_ptr(new B);//可以 }
请记住:
请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数;
如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。
为什么唯有non-menber函数才有能力”在所有实参身上实施隐式类型转换”。
#includeusing namespace std; template class A { public: A(T t) { this->t = t; } A(const A & e) { this->t = e.t; } const A & operator=(const A & e) { this->t = e.t; return *this; } template const A operator*(const A & t1, const A & t2) { return A (t1.t*t2.t); } private: T t; }; int main() { A t1(10); A t2 = t1 * 10;//报错 return 0; }
上述失败的原因是编译器不知道我们想要调用哪个函数,它们试图想出什么函数名为operator*的template具现化。但它们必须要知道T是什么,它们没有这个能耐。
第一个参数t1是A类型的,但第二个参数是int类型,那么编译器如何推算出T?
只需要:template class内的friend声明式可以指涉某个特定函数。那意味class A可以声明operator*为friend函数。所以编译器总是能够在class A具现化时得知T。
#includeusing namespace std; template class A { public: A(T t) { this->t = t; } A(const A & e) { this->t = e.t; } const A & operator=(const A & e) { this->t = e.t; return *this; } friend const A operator*(const A & t1, const A & t2) { return A (t1.t*t2.t); } private: T t; }; int main() { A t1(10); A t2 = t1 * 10;//没报错 return 0; }
请记住:
当我们编写一个class temlate,而它所提供之“与此template相关的”函数支持”所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”
完全看不懂。
主要是搞不明白traits。大概好像是利用模板偏特化使得模板得到类型?
traits
模板与泛型
请记住:
Traits classes使得”类型相关信息”在编译期可用。它们以templates和“templates特化完成实现”。
整合重载技术后,traits classes有可能在编译期对类型执行if。。else测试
Template metaprogramming(TMP 模板元编程)
它有两个伟大效力:
第一,它让某些事情更容易。
第二,由于TMP执行于C++编译期,因此可将工作从运行期转移到编译期。这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。然而将工作从运行期转移到编译期的另一个结果是,编译时间变长了。
好的,我们来写hello world==》阶乘
#includeusing namespace std; template struct Factorial { enum { value = n * Factorial ::value }; }; //特化模板,Factorial<0>的value值为1 template<> struct Factorial<0> { enum { value = 1 }; }; int main() { Factorial<5> a; cout << a.value << endl; return 0; }
有了这个TMP,只要你指涉Factorial::value就可以得到n阶乘值。循环发生在template具现体内部指涉另一个template具现体Factional之时。我们需要一个特殊情况造成递归结束,这里的特殊情况是template特体化Factorial<0>。
此外,为了领悟TMP之所以值得学习,很重要的一点是先对它能够达成什么目标有一个比较好的理解,下面是三个例子:
第一、确保度量单位正确。如果使用TMP,就可以确保(在编译器)程序中所有量度单位的组合都正确,不论其计算多么复杂。这也是为什么TMP可被用来进行早期错误侦测。
第二,可以优化矩阵运算。如果使用高级、与TMP相关的template技术,即所谓的expression templates,就有可能消除多个矩阵相乘时产生的临时对象并合并循环。
第三,可以生成客户定制的设计模式实现品。运用所谓policy-based design的TMP-based技术,有可能产生一些templates用来表述独立的设计选项,然后可以任意结合他们,导致模式实现品带着客户定制的行为。此外也可以用来避免生成对某些特殊类型并不合适的代码。
请记住:
TMP可将工作由运行期转移到编译期,因而得以实现早期错误侦测或者更高的执行效率。
TMP可被用来生成“基于政策选择组合”的客户定制代码,也可以用来避免生成对某些特殊类型并不适合的代码。(这句话看不懂也没关系)



