Effective C++ 第7章读书笔记(总结)
第7章 模板与泛型编程
模板元编程
在C++编译器内执行,并于编译完成时停止执行
条款41:了解隐式接口和编译器多态
编译器多态
哪一个重载函数该被调用
运行期多态
哪一个虚函数该被绑定
条款42:了解typename的双重意义
template<typename C>
void print2nd(const C& container){
if(container.size() > 2){
**typename** C::const_iterator iter(container.begin());
……
}
}
这里的这个typename是必须滴
任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename
- typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可以在member initialization list(成员初始列表)中作为base class修饰符。例如:
template<typename T>
class Derived : public Base<T>::Nested{//这里Base<T>的前面就不能用typename
public:
explicit Derived(int x): Base<T>::Nested(x){//同样,这里Base<T>的前面也不能用typename
typename Base<T>::Nested temp;
//……
}
};
- 常见typename的例子:
template<typename IterT>
void workWithIterator(IterT iter){
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
//……
}
如果IterT是vector::iterator,temp的类型就是int,如果IterT是vector::iterator,temp的类型就是string
这是标准traits class的一种运用
条款43:学习处理模板化基类内的名称
三种办法令C++“不进入 templatized base classes 观察”的行为失效
- 在base class函数调用动作之前加上this->
- 使用using声明式
- 明白指出被调用的函数位于base clase内
条款44:将与参数无关的代码抽离template
共性与变性分析
- 对于函数,抽出两个函数的共同部分,把它们放进第三个函数中。
- 对于class,把共同部分搬到新class中,然后使用继承或复合,令原先的classes取用这共同特性。
template<typename T, std::size_t n>
class SquareMatrix{
public:
void invert();
};
SquareMatrix<double, 5> 和SquareMatrix<double, 10>会具现化两份invert,这两份invert只有常量5和10不同,其余的内容完全相同
所以,进行改进:
template<typename T>
class SquareMatrixBase{
protected://注意这里是protected
void invert();
T* pData;
};
template<typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T>{
private:
using SquareMatrixBase<T>::invert;
T data[n * n];
public:
void invert(){this->invert(n);}//这是一个inline调用,this的作用防止invert这个名字被派生类掩盖
};
上面这种方法也有缺点,可能会造成对象所需的内存变大:
base class为了处理矩阵数据,需要一个T*指针,因此,一个SquareMatrix 对象会增大一个指针的大小
???? 请记住
- templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系
- 因非类型模板参数(上面示例代码中的std::size_t n)而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数
- 因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(eg:指针)的具现类型共享实现码
条款45:运用成员函数模板接受所有兼容类型
同一个template的不同具现体之间并不存在什么与生俱来的固有关系(这里意指如果带有base-derived关系的B、D两类型分别具现化某个template,产生出来的两个具现体之间并不带有base-derived关系)
如果需要template产生出的具现体之间带有base-derived关系 ,需要为它写一个构造模板:
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other)//这个也叫**泛化copy构造函数**,这里不需要explicit,因为是隐式转换
:heldPtr(other.get()){ //存在某个隐式转换可以将一个U*指针转为一个T*指针时才能通过编译
//……
}
private:
T* heldPtr
}
???? 请记住
- 请使用成员函数模板生成“可接受所有兼容类型”的函数
- 如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符
条款46:需要类型转换时请为模板定义非成员函数
- template实参推导过程中不将隐式类型转换函数纳入考虑
template<typename T>
class Rational{
public:
//……
friend const Rational operator*(const Rational& lhs,const Rational& rhs){//这里的Rational后面的<T>可以加,也可以像这样省略
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}
};
这里虽然使用了friend,却与friend的传统用途“访问class的non-public成分”毫不相干。
为了让类型转换可能发生与所有实参身上,我们需要一个non-member函数;
为了令这个函数被自动具现化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是令它成为一个friend。
???? 请记住
当我们编写一个class template,而它所提供的之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
条款47:请使用traits classes表现类型信息
Traits是一种技术,也是一个C++程序员共同遵守的协议,它们允许你在编译期间取得某些类型信息
tratis引发“编译期发生与类型身上的if……else计算”(条款48)
如何使用一个traits class:
- 建立一组重载函数(身份像劳工)或函数模版,彼此间的差异只在于各自的tratis参数。令每个函数实现码与其接受值traits信息 相 应和
- 建立一个控制函数(身份像工头)或函数模版,它调用上述那些“劳工函数”并传递tratis class所提供的信息
???? 请记住
- Tratis classes使得“类型相关信息”在编译期可用。它们以“templates”和“templates特化”完成实现
- 整合重载技术后,tratis class有可能在编译期对类型执行if……else测试
条款48:认识template元编程
Template metaprogramminh(TMP,模版元编程)
Template metaprogramminh执行于C++编译期,会导致以下结果:
- 可以在编译期找出代码错误
- 较小的可执行文件、较短的运行期、较少的内存需求
- 编译时间变长
TMP的起手程序:在编译期计算阶乘:
template<unsigned n>
struct Factorial
{
enum {value = n * Factorial<n-1>::value};
};
template<>
struct Factorial<0> //一个特化版本结束递归
{
enum {value = 1};//特殊情况
};
int main()
{
cout << Factorial<5>::value << endl;
cout << Factorial<20>::value << endl;
return 0;
}
执行结果:
TMP能够达成什么目标?
- 确保量度单位正确
- 优化矩阵运算
- 可以生成客户定制之设计模式
???? 请记住
- TMP可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率
- TMP可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码
推荐阅读
-
《Effective C++》读书笔记 资源管理
-
《Effective C++》读书笔记 被你忽略的关于构造析构赋值
-
C++ primer读书笔记第13章:拷贝控制
-
Effective C++ 读书笔记 Item26 为什么要推迟变量的定义?
-
《Effective C++》读书笔记
-
[读书笔记] - 《深度探索C++对象模型》第3章 Data语意学
-
C++程序员应了解的那些事(36)Effective STL第6条:当心C++编译器中最烦人的分析机制 --- 调用构造函数被误认为是函数声明的问题
-
《Effective C++》条款04总结
-
effective c++条款总结(5)
-
《Effective C++》学习总结(条款06 - 10)