Effective C++ 3nd 读书摘要(七、模板与泛型编程)Item41 - 48

来源:互联网 发布:8个字 网络流行语 编辑:程序博客网 时间:2024/05/14 12:57

七、模板与泛型编程
C++ template mechanism is itself Turing-complete: it can be used to compute any computable value.

Item41. 了解隐式接口和编译期多态
对class而言,接口是显式的,以函数签名为中心;多态是通过virtual函数发生于运行期。


对template来讲,接口是隐式的,奠基于有效表达式,在编译期完成检查;多态通过templace具现化和函数重载解析发生于编译期。

 

Item42. 了解typename的双重意义
任何时候想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename:(缺省状态下将不被认为是类型)

typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list中作为base class修饰符,如:

 

Item43. 学习处理模板化基类内的名称
比如有两个公司

MsgSender向外发送信息。现在从MsgSender派生出一个类,可以志记:

这段代码将无法通过编译,因为不到具现化,LoggingMsgSender就无法知道MsgSender<Company>看起来像什么,确切地说是无法知道MsgSender<Company>是否有个可以供LoggingMsgSender调用的sendClear函数。这似乎与直觉违背,因为模板定义中确实有sendClear,但不要忘了模板是可以特化的

 

假设有个CompanyZ坚持只有加密通讯:

因为CompanyZ没有sendCleartext函数,所以MsgSender template对它不适用,可以针对CompanyZ产生一个MsgSender特化版:(这里是全特化

那么在上面出错的代码中,如果Company == CompanyZ,则就没有sendClear可以调用。

 

有两个比较好的办法可以纠正:

以上两种方法都是对编译器承诺“base class template的任何特化版本都将支持其一般(泛化)版本所提供的接口”。


不过编译器也不会乱搞,如果你打算这样:

LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
...                                 // put info in msgData
zMsgSender.sendClearMsg(msgData);            // error! won't compile

Item44. 将与参数无关的代码抽离templates
例如:

这个template中的size_t就是与类型无关的。


一种改动方案可能是这样的:

这将允许derived classes改变矩阵大小,并决定内存分配方式。


可以在栈上分配:

也可以在堆上分配:

 

Item45. 运用成员函数模板接受所有兼容类型

即所谓member function templates。以上代码的含义是:对任何类型T和任何类型U,可以根据SmartPtr<U>生成一个SmartPtr<T>。

 

member function templates还可用于支持赋值操作:

注意以上唯有generalized copy constructor没有用户explicit。另外注意TR1::shared_ptrs和tr1::weak_ptrs在传输时,都有用const,而auto_ptr没有,因为在复制一个auto_ptr时,其实是被改动了。

 

在class内声明generalized copy constructor并不会阻止编译器生成它们自己的copy constructor,所以如果不想让编译器生成,你必须自己生成:

 

Item46. 需要类型转换时请为模板定义非成员函数(参见Item24)

这样可以通过混合运算:
Rational<int> oneHalf(1,2);
Rational<int> result = 2 * oneHalf;

因为当对象被声明为一个Rational<int>时,Rational<int>就被具现化出来,而作为过程的一部分,friend函数operator*也就被自动声明出来。后者是一个函数而非函数模板,因此编译器可在调用它时使用隐式转换函数(调用Rational的non-explicit构造函数),而这便是混合式调用之所以成功的原因。

 

在operator*中调用一个外部辅助函数可以将inline的冲击减到最小。

doMultiply作为一个template当然不支持混合式乘法,但它只被operator*调用,而后者支持了混合式操作,并将转换后的两个Rational对象传给适当的doMultiply template具现体。

 

Item47. 请使用traits classes表现类型信息
Traits是一种技术,它要求对内置类型和用户自定义类型的表现必须一样好。这种技术就是template加偏特化


考虑STL中的算法advance:
template<typename IterT, typename DistT>       // move iter d units
void advance(IterT& iter, DistT d);            // forward; if d < 0, move iter backward

现在有一组不同的iterator,希望advance针对不同的iterator采取不同的有效的处理手法:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag: public input_iterator_tag {};
struct bidirectional_iterator_tag: public forward_iterator_tag {};
struct random_access_iterator_tag: public bidirectional_iterator_tag {};

我们希望有一种手法可以在编译器获取其信息。

 

现在定义一个模板struct来处理迭代器分类的相关信息:
template<typename IterT>          // template for information about
struct iterator_traits;            // iterator types

iterator_traits的动作方式是:针对每一个传入的IterT,都必须声明一个typedef为iterator_category,用来确认IterT的迭代器分类。


比如,对于deque的迭代器是可随机访问的,则:

而list的迭代器是可双向行进的,则:

至于iterator_traits的作用就是重复一下IterT是个什么:

以上是针对用户自定义类型的,对指针这种迭代器,因为它不能嵌套typedef,所以需要一个偏特化:

现在可以针对不同的iterator定义出一系列重载函数:

由于forward_iterator_tag继承自input_iterator_tag,所以上述doAdvance的input_iterator_tag版本也能够处理forward迭代器,这是public继承(is-a)的一个好处。

 

那么对advance,只要在其中调用doAdvance即可:

小结:一个traits class的用处为两点:
1. 建立一组重载函数或模板,彼此间的差异只在于各自的traits参数。
2. 建立一个控制函数或模板,它调用1中的函数并传递traits class所以提供的信息。

 

TR1引入了许多新的traits classes用以提供类型信息,有:is_fundamental<T>、is_array<T>、is_base_of<T1, T2> ......,共有50个以上。

 

Item48. 认识template元编程(TMP扫盲)
TMP:编写template-based C++程序并执行于编译期。

这样可以在编译期侦测出通常在运行期才能侦测到的错误;使用TMP的C++程序有较小的可执行文件、较短

的运行期、较少的内存需求。只是编译时间变长了。
Item47的traits解法就是TMP——它是编译期发生于类型身上的if...else计算


一个简单的例子:

 

TMP可以做以下三种事:
1. 确保度量单位正确。
2. 优化矩阵运算。
3. 可以生成客户定制之设计模式实现品。这项技术已经成为generative programming(殖生式编程)的一个基础。


TMP will probably never be mainstream, but for some programmers
 — especially library developers — it's almost certain to be a staple.

原创粉丝点击