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

白昼怎懂夜的黑 提交于 2020-01-09 21:11:17

条款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资格修饰符”完成。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!