条款41:了解隐士接口和编译期多态
Template及泛型编程的世界,与面向对象有根本上的不同,它存在隐式接口和编译期多态。如下代码:
template<typename T>
void doProcessing(T& w){
if (w.size() > 10 && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}
- 本例中,w必须支持size,normalize和swap成员函数、copy构造函数、不等比较。这一组表达式(对template而言必须有效编译)便是T必须支持的一组隐式接口。
- 凡涉及w的任何函数调用,例如operate>和operate!=,有可能造成template具现化,使这些调用得以成功。这样的具现行为发生在编译期。“”以不同的template参数具现化function templates”会导致调用不同的函数,这便是所谓的编译期多态。
显示接口由函数的签名式(函数名称、参数类型、返回类型)构成。隐式接口就完全不同了。他是由有效的表达式组成:
请记住
- classes和templates都支持接口和多态。
- 对classes而言接口是显示的,一函数签名为中心,多态则是通过virtual函数发生于运行期。
- 对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。
条款42:了解typename的双重意义
对于template声明式中,class和typename有什么不同?
template<class T> class Widget;
template<typename T> class Widget;
其实两者并没有什么不同,当我们声明template类型参数,class和typename的意义完全相同。
然而C++并不总是把class和typename视为等价。
template<typename C>
void print2nd(const C& container)
{
if(container.size() >= 2){
C::const_iterator iter(container.begin());
}
}
如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非你告诉它是。所以缺省情况下嵌套从属名称不是类型。因此我们需要告诉C++它是类型,此时只需要在紧邻它之前放置关键字typename即可:
typename C::const_iterator iter(container.begin());
然而在typename不能出现在base class list中以及成员初值列表中作为基类修饰符。
template<typename T>
class Derived: public Base<T>::Nested { //base class list不允许出现typename
public:
explicit Derived(int x)
:Base<T>::Nested(x) //成员初值列表,不允许typename
{
typename Base<T>::Nested temp; //嵌套从属类型名称,此处允许typename
}
};
typedef和typename一起使用用来指涉一个名称为成员类型而非成员变量,并为其设定typedef名称(简化):
template<typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
}
请记住
- 声明template参数时,前缀关键字class和typename可互换。
- 请使用关键字typename标识嵌套从属类型名称:但不得在base class lists及成员初值列内以它作为修饰符。