三年来的实践,遇到的模版编程并不是很多,只是在最近的一个电信项目中,看到了高超的模版技术应用,十分感慨,这里对于模版的总结,完全参照Effective C++ 第三版。
隐式接口和编译期多态
1.classes和template都支持接口(interfaces)和多态(polymorphism)。
2.对于classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生在运行期。
3.对于template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template实例化和函数重载解析(function overloading resolution)发生于编译期。
了解typename
1.声明template参数时,前缀关键字class和typename可以互换。
2.需要使用关键字typename标识嵌套从属类型名称;但是不能在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class的修饰符。
所谓从属名称,就是在template内出现的依赖于某个template参数的名称。
关于typename,如下
template
class Derived : public Base
{
public:
explicit Derived(int x)
: Base
{
typename Base
... ...
}
};
3. 处理模版化基类内的名称
当模版存在继承的时候,一定会导致继承自基类的名称无法通过编译,有三个方法可以解决该问题:
我们假设有这样一个基类模版
template
class MsgSender
{
public:
... ...
void sendClear(const MsgInfo & info) // 继承类中将要调用的函数
{
// send message.
std::string msg = info.message();
T object;
object.sendText(msg);
}
};
当继承类需要使用函数sendClear时,编译时无法通过的,需要做如下选择:
1.在base class函数调用动作之前加上”this->”:
template
class LoggingMsgSender : public MsgSender
{
public:
... ...
void sendClearMsg(const MsgInfo & info)
{
// do something first
this->sendClear(info); //使用“this->”的调用
// do something finally
}
};
2.使用using声明式可以做到:
template
class LoggingMsgSender : public MsgSender
{
public:
using MsgSender
... ...
void sendClearMsg(const MsgInfo & info)
{
// do something first
sendClear(info); // 这里可以调用成功
// do something finally
}
};
这里并不是base class内的名称被derived class名称遮掩,而是编译器根本就不进入base class作用域内查找,我们需要通过using告诉编译器,请它那么做。
3.明白指出被调用函数位于base class内:
template
class LoggingMsgSender : public MsgSender
{
public:
... ...
void sendClearMsg(const MsgInfo & info)
{
// do something first
MsgSender
// do something finally
}
};
这种做法还是欠妥,因为如果被调用的是个virtual函数,上述的明确资格修饰会关闭”virtual绑定行为”。
将与参数无关的代码抽离template
1.template会生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
2.因非类型参数模版而造成的代码膨胀,往往可以消除,做法就是以函数参数或者class成员变量替换template参数。
3.因类型参数(type parameters)而造成的代码膨胀,往往可以降低,做法是让带有完全二进制表述的具现类型共享实现码。
运用成员函数模版接受所有兼容类型
1.请使用member function template(成员函数模版)生成“可接受的所有兼容类型”的函数。
2.如果我们声明member template用于“泛化 copy函数”或“泛化assignment操作”,那么还需要声明正常的copy函数和copy assignment操作符。
需要类型转换时请为模版定义非成员函数
当我们编写一个class template,而它所提供的“与此template相关的”函数支持“所有参数的隐式转换”时,请将那些函数定义为“class template内部的friend函数”。
典型的例子是:
template
//helper template
template
const Rational
// template class Rational definition
template
class Rational
{
public:
... ...
friend const Rational
{
}
};
使用traits classes表现类型信息
1.traits classes使得“类型相关信息”在编译期可用。
他们以template和“templates特化”完成实现。
如何设计并实现一个traits classes
首先,确认若干你希望将来可取得的类型相关信息;
其次,为该信息选择一个名称;
第三,提供一个template和一组特化版本,内含我们希望支持的类型信息。
2.整合重载技术后,traits classes有可能在编译期对类型进行测试。
如何使用一个traits classes
首先,建立一组重载函数或函数模版,彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受的traits信息相符合;
其次,建立一个控制函数或函数模版,它调用上述那些函数并传递traits class所提供的信息。
认识template元编程
Template metaprogramming(TMP)是编写template-based C++程序并执行于编译期的过程。
TMP两大效力:第一,它让某些事情更加容易。如果没有它,那些事情将是困难的,甚至是不可能的。第二,由于template metaprogramming执行于C++编译期,因此可将工作从运行期转移至编译期。这将导致一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一个方面都更高效:较小的可执行文件,较短的运行期,较少的内存需求。
TMP的一个典型例子是求阶乘:
template
struct Factorial {
enum{ value = n * Factorial
};
template<>
struct Factorial<0> {
enum { value = 1 };
};
使用方式如下:
Factorial<10>::value; 这样就可以得到10的阶乘了
主线:《Effective C++》 Third Edition
-- 转载请注明出处