[笔记]《Effective C++》第七章 Templates and Generic Programming

C++ template机制自身是一部完整的图灵机(Turing-complete):它可以被用来计算任何可计算的值。

条款41:Understand implicit interfaces and compile-time polymorphism.

  1. classes和templates都支持接口(interfaces)和多态(polymorphism)。
  2. classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期
  3. template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化函数重载解析(function overloading resolution)发生于编译期
  • 在OOP(面向对象编程)中,总是以显式接口(explicit interfaces)和运行期多态(runtime polymorphism)解决问题。
  • 在Templates及泛型编程中,显式接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口(implicit interfaces)和编译期多态(compile-time polymorphism)移到前头了。
    • 以不同的template参数具现化function templates”会导致调用不同的函数,这便是所谓的编译期多态(compile-timepolymorphism)。
    • 通常显式接口由函数的签名式(也就是函数名称参数类型返回类型)构成。
class Widget{
public:
  Widget();
  virtual ~Widget();
  virtual std::size_t size() const;
  virtual void normalize();
  void swap(Widget& other);
};
  • 隐式接口并不基于函数签名式,而是由有效表达式(valid expressions)组成。
template<typename T>
void doProcessing(T& w)
{
  if(w.size() > 10 && w != someNastyWidget)
  {
    T temp(w);
    temp.normalize();
    temp.swap(w);
  }
}
  • 加诸于template参数身上的隐式接口,就像加诸于class对象身上的显式接口一样真实,而且两者都在编译期完成检查

条款42:Understand the two meanings of typename.

  1. 声明template参数时,前缀关键字classtypename可互换。
  2. 请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)member initialization list(成员初值列)内以它作为base class修饰符
  • template内出现的名称如果相依于某个template参数,称之为从属名称(dependent names)。如果从属名称在 class 内呈嵌套状,我们称它为嵌套从属名称(nested dependent name)。
  • C++有个规则可以解析(resolve)此一歧义状态:如果解析器在template中遭遇一个嵌套从属名称,它便假设这名称不是个类型,除非你告诉它是。
  • 任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字 typename

条款43:Know how to access names in templatized base classes.

我们必须有某种办法令C++“不进入templatized base classes观察”的行为失效。

  • 第一是在base class函数调用动作之前加上"this->":
template< typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info)
  {
    //将“传送前”的信息写至log;
    this->sendClear(info);  //成立,假设sendClear将被继承。
    //将“传送后”的信息写至log;
  }
  ...
};
  • 第二是使用using声明式。
template< typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  using MsgSender<Company>::sendClear;  //告诉编译器,请它假设sendClear位于base class内。
  ...
  void sendClearMsg(const MsgInfo& info)
  {
    ...
    sendClear(info);  //成立,假设sendClear将被继承。
    ...
  }
  ...
};
  • 第三个做法是,明白指出被调用的函数位于baseclass内。但这往往是最不让人满意的一个解法,因为如果被调用的是virtual函数,上述的明确资格修饰(explicit qualification)会关闭“virtual绑定行为”
template< typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info)
  {
    ...
    MsgSender<Company>::sendClear(info);  //成立,假设sendClear将被继承。
    ...
  }
  ...
};

条款44:Factor parameter-independent code out of templates.

  1. Templates生成多个classes和多个函数,所以任何template代码不该与某个造成膨胀的template参数产生相依关系
  2. 非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数class成员变量替换template参数。
  3. 类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码

条款45:Use member function templates to accept"all compatible types."

  1. 请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。
  2. 如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数copy assignment操作符
  • STL容器的迭代器几乎总是智能指针

条款46:Define non-member functions inside templates when type conversions are desired.

  • 当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template 内部的friend函数”。
  • 在template实参推导过程中从不将隐式类型转换函数纳入考虑。
  • template class内的friend声明式可以指涉某个特定函数。

条款47:Use traits classes for information about types.

  1. Traits classes使得“类型相关信息”在编译期可用。它们以templates和“templates特化”完成实现。
  2. 整合重载技术(overloading)后,traits classes有可能在编译期类型执行if...else测试。
  • STL迭代器分类:
    • Input迭代器:只能向前移动,一次一步,客户只可读取(不能涂写)它们所指的东西,而且只能读取一次
    • Output迭代器:只能向前移动,一次一步,客户只可涂写它们所指的东西,而且只能涂写一次
    • forward迭代器:可以做前述两种分类所能做的每一件事,而且可以读或写其所指物一次以上
    • Bidirectional迭代器:除了可以向前移动,还可以向后移动。
    • random access迭代器:可以在常量时间内向前或向后跳跃任意距离
    • 所有forward迭代器都是input迭代器,依此类推
  • 如何设计并实现一个traits class:
    • 确认若干你希望将来可取得的类型相关信息
    • 为该信息选择一个名称(例如iterator_category)。
    • 提供一个template和一组特化版本,内含你希望支持的类型相关信息
  • 如何使用一个traits class:
    • 建立一组重载函数(身份像劳工)或函数模板,彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受之traits信息相应和。
    • 建立一个控制函数(身份像工头)或函数模板,它调用上述那些“劳工函数”并传递traits class所提供的信息。

条款48:Be aware of template metaprogramming.

  • Template metaprogramming(TMP,模板元编程)是编写template-based C++程序并执行于编译期的过程。
  • 所谓template metaprogram(模板元程序)是以C++写成执行于C++编译器内的程序。一旦TMP程序结束执行其输出,也就是从templates具现出来的若干C++源码,便会一如往常地被编译
  • TMP的优缺点:
    • 让某些事情更容易。
    • 可将工作从运行期转移到编译期。某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。
    • 更高效:较小的可执行文件、较短的运行期、较少的内存需求
    • 编译时间变长
posted @ 2021-09-30 16:35  浮生的刹那  阅读(67)  评论(0)    收藏  举报