7 模板和泛型编程
条款41:了解隐式接口和编译器多态
面向对象编程世界总是以显式接口和运行期多态解决问题:
class Widget{
public:
Widget();
virtual ~Widget();
virutal std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
//...
};
有这样一个函数:
void doProcessing(Widget& w){
if(w.size() > 10 && w != someNastyWidget){
Widget temp(w);
temp.normalize();
temp.swap(w);
}
}
现在我们可以这样说w:1.w这是一个显式接口。
2.Widget的某些成员函数是virtual,w对这些函数的调用将表现出运行期多态。
Template及泛型编程的世界,与面向对象有根本上的不同。此时,显式接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口和编译器多态移到了前头。如果把上述函数变成函数模板:
template<typename T>
void doProcessing(T& w){
if(w.size() >10 && w != someNastyWidget){
T temp(w);
temp.normalize();
temp.swap(w);
}
}
现在可以这么说w:
1. w必须支持哪一种接口,系有template中执行于w身上的操作来决定。本例来看w的类型T好像必须支持size,normalize和swap成员函数、copy构造函数、不等比较。我们很快会看到这并非完全正确,但对目前而言足够真实。重要的是,这一组表达式便是T必须支持的一组隐式接口。
2.凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template instantiated,使这些调用得以成功。这样的instantiated行为发生在编译期。以不同的template参数instantiated function templates,会导致调用不同的函数,这便是所谓的编译期多态
通常显式接口由函数的签名式(函数名,参数类型,返回类型)构成:
class Widget{
public:
Widget();
virtual ~Widget();
virutal std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
//...
};
隐式接口就完全不同了。它并不基于函数签名式,而是由有效表达式(valid expression)组成。例如上述模版函数中T(w的类型)的隐式接口看来好像有这些约束:
1.它必须提供一个名为size的成员函数,该函数返回一个整数值。
2.它必须支持一个operator!=函数,用来比较两个T对象。
但是并非如此:(以下没看懂)
真要感谢操作符重载带来的可能性,这两个约束都不需要满足。
同样的道理,T并不需要支持operator!=。
当人们第一次以此种方式思考隐式接口,大多数会感到头疼。但是,隐式接口仅仅是一组有效表达式构成,表达式自身可能看起来很复杂,但它们要求的约束条件一般而言相当直接又明确(?????)
加诸于template参数身上的隐式接口,就像加诸于class对象身上的的显式接口一样真实,而且两者都在编译期完成检查。
请记住:
1.class和template都支持接口和多态
2.class而言接口是显式的,以函数签名位中心。多态则是通过virtual函数发生于运行期。
3.对template参数而言,接口是隐式,基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。
条款42:了解typename的双重意义
当我们声明template类型参数,class和typename的意义完全相同。然后C++并不是总把typename和class视为等价。有时候你一定得使用typename。假设我们有个template function,接受一个STL兼容容器为参数,容器内持有的对象被赋值为int:
template<typename C>
void print2nd(const C& container){
if(container.size() >= 2){
C::const_iterator iter(container.begin());
++iter;
int value = *iter;
std::cout << value;
}
}
上述代码中的两个local变量iter和value。iter的类型是C::const_iterator,实际是什么取决于template参数C。template内出现的名称如果相依于某个template参数,称之为从属名。如果丛书名称在class内呈嵌套状,称之为嵌套从属名。C::const_iterator就是这样一个名称。而value,类型是int,不依赖于任何template参数的名称,这样的叫做非从属名称。
嵌套从属名有可能导致解析困难,例如:
template<typename C>
void print2nd(const C& container){
C::const_iterator* x;
}
看起来好像声明x为一个local变量,但它之所以被那么认为,只因为我们已经知道C::const_iterator是个类型。如果C::const_iterator不是个类型呢?如果C有个static成员变量恰好被命名为const_iterator,那样上述代码就不再是声明为一个local变量,而是一个相乘动作。现在看看print2nd起始处:
template<typename C>
void print2nd(const C& container){
if(container.size() >= 2){
C::const_iterator iter(container.begin());//这个名称被假设为非类型(??????)
}
}
现在清楚为什么这不是有效的C++代码了吧。iter声明式只有在C::const_iterator是个类型时才合理,但我们并没有告诉C++说它是,于是C++假设它不是。若要矫正这个形式,我们必须告诉C++说C::const_iterator是个类型。只要在它之前加上typename即可:
template<typename C>
void print2nd(const C& container){
if(container.size() >= 2){
typename C::const_iterator iter(container.begin());
++iter;
int value = *iter;
std::cout << value;
}
}
一般性规则很简单:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放置上typename。
以下内容没看懂,不抄了
typename必须作为嵌套从属类型名称的前缀词这一规则的例外是,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){ //成员初始列中不允许tepename
typename Base<T>::Nested temp; //既不在base class list中也不在mem.init.list中
//..
}
//..
}
请记住:
1.声明template参数时,关键字class和typename可互换。
2.请使用关键字typename标识嵌套从属类型名;但不得在base class list或者member initialization list内以它作为base class修饰符。