zoukankan      html  css  js  c++  java
  • 七、模板与泛型编程--条款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资格修饰符”完成。

  • 相关阅读:
    使用 kill 命令杀死 java进程,你用对了吗?
    脚本 启动/停止 jar包服务
    Zipkin和微服务链路跟踪
    nacos初探--作为配置中心
    第一次有人把“分布式事务”讲的这么简单明了
    SquishIt引起的HTTP Error 500.0
    imagesLoaded – 检测网页中的图片是否加载
    25个精美的创意机构和设计工作室网站案例
    使用 FocusPoint.js 实现图片的响应式裁剪
    设计师收藏的20款英文手写字体【免费下载】
  • 原文地址:https://www.cnblogs.com/love-jelly-pig/p/9711940.html
Copyright © 2011-2022 走看看