条款41:了解隐式接口和编译期多态
从一个函数解读隐式接口和编译期多态:
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函数,也要支持比较函数,这就是T必须支持的一组隐式接口。
编译期多态: 涉及w对象的任何调用,都有可能造成template的具现化。这样的具现化发生在编译期,也就是编译期多态。
tips:上述的具现化也就是我们调用的时候,具体得到T的类型是什么,然后会调用相应的函数。
不一样之处
在上述代码看来,T类型的对象要支持size函数。但是这个函数必须返回一个数值类型吗?不一定!只需要返回的类型为X的对象,X+10能够调用一个operator>即可! 更甚至,不需要返回一个X的类型,返回一个Y类型,此Y类型可以隐式转为X即可!
作者总结
classes和templates都支持接口和多态。
对classes而言接口是显式的,以函数签名为中心。多态是通过virtual函数发生于运行期。
对templates参数而言,接口是隐式的,奠基于有效表达式。多态是通过templates具现化和函数重载解析发生于编译期。
条款42:了解typename的双重定义
事实上,使用模板时有两种关键字写法:
template<class T> class Widget;
template<tyoename T> class Widget;
这两种写法毫无区别! 但是在其它情况,typename还有别的用途。
一、了解从属名称和嵌套从属名称
老规矩,我们先看代码:
template<typename C>
void print2nd(const C &container)
{
if(container.size() >= 2)
{
C::const_iterator iter(container.begin());
++iter;
int value = *iter;
cout << value;
}
}
从属名称: template内出现的名称如果依赖于某个template参数,称之为从属名称。如上述的iter,它类型是C::const_iterator,取决于参数C的类型。
嵌套从属名称: 如果从属名称在class内呈嵌套状,就称为嵌套从属名称。如C::const_iterator就是个嵌套从属名称。实际上这还是个嵌套从属类型名称, 它表示的是一个类型。
二、嵌套从属名称带来的歧义性
编译器没有我们想象中的那么灵活,使用嵌套从属名称可能会给编译器带来解析困难。如果我们在上述函数的函数体中有一个这样的语句:
C::const_iterator *x;
我们本意是想声明一个x的迭代器指针,但是编译器可能不那么想。它可能想的是const_iterator这个变量和x变量相乘!对,就是把 * 看成了乘号而不是一个指针类型。
也就是说,如果解析器在template中遭遇一个嵌套从属名称,它便假设这不是个类型,除非你告诉它是。
解决方法:typename关键字
使用typename,如:
typename C::const_iterator *x;
在声明前面加上关键字typename就可以让编译器把C::const_itrator当作一个类型来看。
实际上,我在VS2013中测试的时候,在此编译器中是不需要这样声明的。
一个例外
在代码中,并不总是需要加上typename关键词。typename不可以出现在base class list内的嵌套从属类型名称之前,也不可以出现在member initialization list(成员初始列)中作为base class的修饰符。
class Derive : public Base<T>::Nested // 无typename关键字
{
public:
Derived(int x)
: Base<T>::Nested(x) // 无typename
{
typename Base<T>::Nested temp; // 有typename
...
}
};
以上代码可以清楚带我们看到哪些时候要用typename,哪些时候不用。
作者总结
声明template参数时,前缀关键字class和typename可互换。
请使用关键字typename标识嵌套从属类型名称,但不得在base class list或member initialization list内以它作为base class的修饰符。
条款43:学习处理模板化基类内的名称
先考虑一段代码:
class CompanyA
{
public:
void sendClearText(string &msg)
{
cout << "it's A " << msg << endl;
}
void senEncrypted(string &msg)
{
cout << msg << msg << endl;
}
};
class CompanyB
{
public:
void sendClearText(string &msg)
{
cout << "it's B " << msg << endl;
}
void senEncrypted(string &msg)
{
cout << msg << msg << endl;
}
};
class Msg
{
public:
Msg(string s) : str(s)
{
}
string str;
};
template<typename T>
class Base
{
public:
void sendClear(const Msg& info)
{
string msg=info.str;
T t;
t.sendClearText(msg);
}
};
template<typename T>
class Derived : public Base < T >
{
public:
void sendClearMsg(const Msg &info)
{
sendClear(info); // 这句编译不过
}
};
按照作者在书上说法,这段应该是编译不过的。原因在于Derived类中调用了sendClear函数。
原因
它继承的基类是一个模板类,不到具现化的时候无法知道T的类型是什么,更准确的说是不知道它是否有一个sendClear的成员函数。 所以编译器不会到基类的作用域中去寻找是否有这个函数。
本质原因则是因为编译器无法知道是否有个模板类,它是专属的全特化版本,里面是去掉了sendClear类型的。所以编译器拒绝这个调用。因为不到具现化,谁也不知道这个函数是否真的存在。
问题
我在VS2013中测试,上段代码是可以正确执行的:
是否此条款已经过时了呢?现在的编译器是不是可以正确的判断了呢?这个我现在还不能分辨清楚,以后学编译上的东西会再看看。
处理方法:
如果是按照作者书上所说的,那么作者同时也提供了三种方法来解决(告诉编译器有这个东西,让它去寻找):
(1) 在调用之前加上this,编译器就会去基类寻找,否则不会。
(2) 使用using声明式,让编译器去base class中去寻找。
(3) 调用前加上base类的作用域。即Base
作者总结
可在derived class templates内通过“this->”指涉base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。