七、模板与泛型编程--条款41-43

条款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::function.

作者总结

可在derived class templates内通过“this->”指涉base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。

原文地址:https://www.cnblogs.com/love-jelly-pig/p/9711940.html