模板元编程:在c++编译器内执行并于编译完成时停止执行

1.了解隐式接口和编译期多态

面向对象编程总是以显式接口(由函数名称、参数类型和返回类型构成)和运行期多态(虚函数)解决问题

模板及泛型编程:对template参数而言,接口是隐式的,基于有效表达式的约束,而多态则是通过模板实例化和函数重载解析发生于编译期。

隐式接口和显式接口两者都在编译期完成检查。

隐式接口:

  1. template<typename T>
  2. void doProcessing(T &w) {
  3. // 隐式接口,对模板参数类型T的约束
  4. if (w.size() > && w != someNastyWidget) {
  5. // ...
  6. }
  7. }

2.了解typename的双重意义

当我们声明template类型参数,class和typename的意义完全相同

template内出现的名称如果相依于某个template参数,称之为从属名称,如果从属名称在class内呈嵌套状,称之为嵌套从属名称。c++规定:如果解析器在template中遭遇一个嵌套从属名称,它便假设这名称不是个类型,除非你告诉它是。

任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放上关键字typename,typename只被用来验明嵌套从属类型名称,其他名称不该有它存在

  1. template <typename C>
  2. void f(const C& container, //不允许使用typename
  3. typename C::iterator iter); //一定要使用typename

typename必须作为嵌套从属类型名称的前缀词这一规则的例外是,typename不可以出现在base class list内的嵌套从属类型名称之前,也不可在成员初始化列表中作为base class修饰符

  1. template <typename T>
  2. class Derived :public Base<T>::Nested // 不允许typename
  3. {
  4. public:
  5. explicit Derived(int x)
  6. :Base<T>::Nested(x) // 不允许typename
  7. {
  8. typename Base<T>::Nested temp;
  9. }
  10. };
  1. std::iterator_traits<IterT>::value_type //类型为IterT之对象所指之物的类型

真实程序中使用typename的例子:

  1. std::iterator_traits<IterT>::value_type 表示IterT类型对象指向的对象的类型,比如IterTvector<int>::iterator,则value_typeint (类型萃取)
  1. template <typename IterT>
  2. void workWithIterator(IterT iter)
  3. {
  4. typename std::iterator_traits<IterT>::value_type temp(*iter); // value_type是嵌套从属类型名称
  5. }

3.学习处理模板化基类内的名称

base class template有可能被特化,而那个特化版本可能不提供和一般性template相同的接口,故c++往往拒绝在模板化基类内寻找继承而来的名称。假如基类的派生类实例化的模板参数是基类的特化版本对应的参数,即该派生类实际继承自特化版本,然后调用了泛化版本中才有的函数,这样就会出错

解决方案:对编译器承诺base class template的任何特化版本都将支持其一般(泛化)版本所提供的接口

1)在base class函数调用动作之前加上"this ->"

2)使用using声明式

3)明确指出被调用的函数位于base class内即用基类名称去调用该函数,这样会失去多态性

  1. class MsgInfo{};
  2.  
  3. template<typename Company>
  4. class MsgSender {
  5. public:
  6. void sendClear(const MsgInfo &info) {
  7. string msg;
  8. Company c;
  9. c.sendCleartext(msg);
  10. }
  11. };
  12.  
  13. template<typename Company>
  14. class LoggingMsgSender: public MsgSender<Company> {
  15. using MsgSender<Company>::sendClear; // ok,此时sendClearMsg中可以直接调用sendClear
  16. void sendClearMsg(const MsgInfo &info) {
  17. // logging
  18. sendClear(info); // 无法通过编译,因为基类的特化版本中可能没有该接口
  19. this->sendClear(info); // ok
  20. MsgSender<Company>::sendClear(info); // ok,但如果sendClear是虚函数,失去多态性
  21. // logging
  22. }
  23. }

4.将与参数无关的代码抽离template

使用template可能会导致代码(object code)膨胀,template生成多个class和多个函数(实例化模板时的模板参数不同,导致生成不同的class,导致目标码很大),因为非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。

  1. // size_t n为非类型模板参数
  2. template<typename T, size_t n>
  3. class SquareMatrix {
  4. public:
  5. void invert();
  6. };
  7.  
  8. // 调用的invert是两个不同的函数,引发代码膨胀
  9. SquareMatrix<double, > sm1;
  10. sm1.invert(); // 调用SquareMatrix<double, 5>::invert
  11.  
  12. SquareMatrix<double, > sm2;
  13. sm2.invert(); // 调用SquareMatrix<double, 10>::invert
  1. template<typename T>
  2. class SquareMatrixBase {
  3. protected:
  4. void invert(size_t matrix_size);
  5. };
  6.  
  7. // 对于给定类型T,SquareMatrix对象共享同一个基类以及invert方法
  8. template<typename T, size_t n>
  9. class SquareMatrix: private SquareMatrixBase<T> {
  10. private:
  11. // 避免隐藏基类的invert方法
  12. using SquareMatrixBase<T>::invert;
  13. public:
  14. void invert() {
  15. this->invert(n);
  16. }
  17. };

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

同一个template的不同实例化之间并不存在什么固有关系,如果以带有base-derived关系的B、D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系

真实指针支持隐式转换,派生类指针可以隐式转换为基类指针,指向non-const对象的指针可以转换为指向const对象的指针,而智能指针比如shared_ptr都是模板类,若想支持隐式转换,可通过成员函数模板实现(是不是还得实现泛化拷贝复制运算?)

  1. template<typename T>
  2. class SmartPtr
  3. {
  4. public:
    // member function template
  5. template<typename U>
  6. // 对任何类型T和任何类型U,可以根据SmartPtr<U>生成一个SmartPtr<T>,泛化copy构造函数
  7. SmartPtr(const SmartPtr<U>& other);
  8. };

注意:在class内声明泛化copy构造函数(member template)并不会阻止编译器生成它们自己的copy构造函数(non-template),所以如果你想要控制copy控制的方方面面,你必须同时声明泛化copy构造函数和“正常形式的”copy构造函数,相同规则也适用于赋值操作。

6.需要类型转换时请为模板定义非成员函数

当我们编写一个class template,而它所提供的与此template相关的函数支持所有参数可以隐式类型转换时,请将那些函数定义为class template内部的friend函数(参考条款24)

  1. template<typename T>
  2. class Rational {
  3. public:
  4. Rational(const T& numerator = , const T& denominator =);
  5. const T numerator() const;
  6. const T denominator() const;
  7. };
  8.  
  9. template<typename T>
  10. const Rational<T> operator* (const Rational<T> &lhs, const Rational<T> &rhs) {
  11. return Rational<T>(lhs.getNumerator() * rhs.getNumerator(), lhs.getDenominator() * rhs.getDenominator());
  12. }
  13.  
  14. /*
  15. 编译器通过operator*调用动作中的实参类型推导模板类型参数T,由oneHalf可推出operator *的第一个参数中T为int,
  16. 因为在template参数推导过程中从不将隐式类型转换函数纳入考虑,也就是说2不能隐式转换为Rational<int>类型,这样
  17. operator*的第二个参数中T无法推导出来
  18. */
  19. Rational<int> oneHalf(, );
  20. Rational<int> result = oneHalf * ; // 编译出错

为了让类型转换可以发生于所有实参身上,我们需要一个non-member函数(条款24),为了令这个函数被自动具现化,我们需要将它声明在class内部(类模板并不依赖模板实参推导,后者只施行于函数模板身上,此时会隐式调用Rational的构造函数),而在class内部声明non-member函数的唯一方法就是声明它为friend,同时需要在声明的同时提供定义。

  1. template<typename T>
  2. class Rational {
  3. public:
  4. Rational(const T& numerator = , const T& denominator =){
  5. // 如果不加this,成员变量的作用域大于形参的作用域,即下面的两个
  6. // numerator、denominator都被视为形参这个变量
  7. this->numerator = numerator;
  8. this->denominator = denominator;
  9. }
  10. const T getNumerator() const {
  11. return numerator;
  12. }
  13. const T getDenominator() const {
  14. return denominator;
  15. }
    // 定义于class内的函数都暗自成为inline,包括像operator *这样的friend函数
  16. friend const Rational<T> operator*(const Rational &lhs, const Rational &rhs) {
  17. return Rational<T>(lhs.getNumerator() * rhs.getNumerator(), lhs.getDenominator() * rhs.getDenominator());
  18. }
  19. private:
  20. T numerator;
  21. T denominator;
  22. };
  23.  
  24. int main()
  25. {
  26. Rational<int> oneHalf(, );
  27. cout << "oneHalf.numerator: " << oneHalf.getNumerator() << endl;
  28. cout << "oneHalf.denominator: " << oneHalf.getDenominator() << endl;
  29. Rational<int> result = oneHalf * ;
  30. cout << "result.numerator: " << result.getNumerator() << endl;
  31. cout << "result.denominator: " << result.getDenominator() << endl;
  32. return ;
  33. }

7.请使用traits classes表现类型信息

traits并不是c++关键字或一个预先定义好的构件,它是一种技术,也是每一个c++程序员共同遵守的协议,允许你在编译期取得某些类型信息,traits总是被实现为struct,iterator_traits是针对迭代器的traits

iterator_traits的运作方式是,针对每一个类型IterT,在struct iterator_traits<IterT>内一定声明某个typedef名为iterator_category,这个typedef用来确定IterT的迭代器分类。(下面代码中的类中类的成员的访问方式?)。首先它要求每一个用户自定义的迭代器类型必须嵌套一个typedef,名为iterator_category,然后iterator_traits响应iterator class的嵌套式typedef。

  1. // deque为用户自定义类型
  2. template<typename T>
  3. class deque {
  4. public:
  5. class iterator { // 这里没有必要定义一个内部类,直接在deque中声明一个typedef就可以了
  6. public:
  7. typedef random_access_iterator_tag iterator_category;
  8. };
  9. };
  10.  
  11. template<typename IterT>
  12. struct iterator_traits {
  13. typedef typename IterT::iterator_category iterator_category;
  14. };

iterator_traits特别针对指针类型提供一个偏特化版本(指针类型不能像用户自定义类型那样嵌套一个typedef):

  1. template<typename IterT>
  2. struct iterator_traits(IterT*)
  3. {
  4. typedef random_access_iterator_tag iterator_category;
  5. };

总结:

1)traits classes使得类型相关信息在编译器可用,它们以templates和templates特化完成实现

2)整合函数重载技术后,traits classes有可能在编译期对类型执行if...else测试(if语句是在运行期执行的)

【effective c++】模板与泛型编程的更多相关文章

  1. Effective C++ —— 模板与泛型编程(七)

    C++ templates的最初发展动机很直接:让我们得以建立“类型安全”的容器如vector,list和map.然而当愈多人用上templates,他们发现templates有能力完成愈多可能的变化 ...

  2. Effective C++ ——模板和泛型编程

    条款41:了解隐式接口和编译器多态 以public继承的类,

  3. C++ 模板与泛型编程

    <C++ Primer 4th>读书笔记 所谓泛型编程就是以独立于任何特定类型的方式编写代码.泛型编程与面向对象编程一样,都依赖于某种形式的多态性. 面向对象编程中的多态性在运行时应用于存 ...

  4. C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续]

    模板与泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...

  5. C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

    模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一 ...

  6. C++ Primer 学习笔记_77_模板与泛型编程 --实例化

    模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实 ...

  7. C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

    模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还能够仅仅特化push和pop成员.我们将特化push成员以复制字符数组,而且特化pop成员以释放该副本使用的内存: ...

  8. C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

    模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...

  9. C++ Primer 学习笔记_76_模板和泛型编程 --模板定义[继续]

    模板和泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...

随机推荐

  1. laravel composer 扩展包开发(超详细)

    laravel composer 扩展包开发(超详细) 置顶 2018年02月05日 11:09:16 Simael__Aex 阅读数:10396    版权声明:转载请注明出处:http://blo ...

  2. Windows SubSystem for Linux(WSL)设置默认和设置默认登陆用户

    使用wslconfig命令进行管理 1.  设置默认运行的linux系统 wslconfig /setdefault <DistributionName> 正如上面所说,如果执行wslco ...

  3. Vickers Vane Pump - Hydraulic Vane Pump Failure: Cavitation, Mechanical Damage

    One of our readers recently wrote to me about the following questions: “Recently, we purchased a sec ...

  4. pm2 start命令进阶详解

    在node的世界里面,并不存在nginx或者apache,甚至tomcat这种东东.一个node,本身就用几行代码,就可以启动个server进程,监听个端口,为大家提供web服务.这和传统的网站代码的 ...

  5. spring mvc poi excel

    Util类 package com.common.util; public class ExportUtil { private XSSFWorkbook wb = null; private XSS ...

  6. node如何导出数据成为excel格式

    node的应用方式,导出数据 首先,你要把数据库连接上,把你要导的数据表写出来 安装模块 $ npm install sequelize $ npm install mysql $ npm insta ...

  7. Fortran中常用函数列表

    Y=INT(X) 转换为整数 ALL(所有型态) INTEGER Y=REAL(X) 转换为实数 INTEGER REAL Y=DREAL(X) 取复数实部(倍精度) COMPLEX*16 REAL* ...

  8. typedef重复定义 和 error: ‘long long long’ is too long for GCC

    今天发现一个很有意思的编译问题,然后在Stack Overflow上也有看到类似的.就是出现了 long long long 类型错误提示 错误提示如下: /home/yejy/algorithm_a ...

  9. selenium Select下拉框

    先来认识一下下拉框,以百度的“高级设置”为例 介绍两种方法来处理下拉框:使用click事件,使用Select方法 使用click事件 上述下拉框的源代码如下: 虽然我们可以在html源文件中看到sel ...

  10. 6. COLUMN_PRIVILEGES

    6. COLUMN_PRIVILEGES 表COLUMN_PRIVILEGES提供有关列权限的信息.它从mysql.columns_priv系统表中获取其值 . 表COLUMN_PRIVILEGES包 ...