• Effective C++:条款25:考虑写出一个不抛异常的swap函数


    (一)

    缺省情况下swap动作可由标准程序库提供的swap算法完毕:

    namespace std { 
        template<typename T> 
        void swap(T& a, T& b) { 
            T temp(a); 
            a = b; 
            b = temp; 
        } 
    }
    这个函数是异常安全性编程的核心,而且是用来处理自我赋值可能性的一个常见机制

    可是对某些类型而言,这些复制动作无一必要:当中基本的就是“以指针指向一个对象,内含真正数据”那种类型。多为“pimpl手法”(pointer to implementation的缩写)

    <span style="color:#333333;"><span style="font-family:Verdana, Arial, Helvetica, sans-serif;font-size:12px;">class WidgetImpl { 
    private: 
        int a, b, c; 
        std::vector<double> v; 
    }; 
    class Widget { 
    public: 
        Widget(const Widget& rhs); 
        Widget& operator=(const Widget& rhs) { 
            *pImpl = *(rhs.pImpl); 
        } 
    private: 
        WidgetImpl* pImpl; 
    };</span></span>
    要置换两个Widget对象值。唯一要做的就是置换pImpl指针,缺省的swap算法不知道这一点。不仅仅复制3个Widget还复制3个WidgetImpl对象。很缺乏效率!

    解决的方法:

    我们能够令Widget声明一个swap的public成员函数做真正的替换工作,然后将std::swap特化。令他调用该成员函数:

    <span style="color:#333333;"><span style="font-family:Verdana, Arial, Helvetica, sans-serif;">class Widget { 
    public: 
        Widget(const Widget& rhs); 
        Widget& operator=(const Widget& rhs) { 
            *pImpl = *(rhs.pImpl); 
        } 
        void swap(Widget& other) { 
            using std::swap; 
            swap(pImpl, other.pImpl); 
        } 
    private: 
        WidgetImpl* pImpl; 
    };
    
    </span></span><pre name="code" class="cpp" style="line-height: 19px; text-align: justify;">class WidgetImpl { 
    private: 
        int a, b, c; 
        std::vector<double> v; 
    }; 
    namespace std{ template<> void swap<Widget>(Widget& a, Widget& b) { a.swap(b); } }
    
    

    这样的做法不仅仅可以通过编译,还与STL容器有一致性。由于全部STL容器也都提供有public swap成员函数和std::swap特化版本号(用以调用前者)。


    (二) 

    如果Widget和WidgetImpl都是class templates而非classes:

    <span style="color:#333333;"><span style="font-family:Verdana, Arial, Helvetica, sans-serif;">template <typename T>
    class WidgetImpl{…};
    template <typename T>
    class Widget{…};</span></span>
    所谓的“partially specialize“:C++同意对类模板进行”部分特化“。但不同意对模板函数进行”部分特化“

    以下的定义就是错误的(将上例中的Widget类写成模板类):

    namespace std{
    	template<typename T>
    	void swap<Widget<T>>(Widget<T>& a, Widget<T>& b){		//错误。不合法!

    a.swap(b); } }

    即使加入重载版本号也行不通。由于标准命名空间是一个特殊的命名空间,客户能够全特化里面的模板,可是不能加入新的模板(包含类模板和函数模板)到标准命名空间中去。所以以下这样的方法不行!
    namespace std{
    	template<typename T>
    	void swap(Widget<T>& a, Widget<T>& b){		//试图重载,不合法。
    		a.swap(b);
    	}
    }
    一般而言,重载function template没有问题,但std是个特殊的命名空间。管理也就比較特殊。客户能够全特化std内的templates。但不能够加入新的templates(或class或function或不论什么其它东西)到std里头。事实上跨越红线的程序差点儿仍可编译运行,但他们行为没有明白定义。所以不要加入不论什么新东西到std里头

    解决方法:

    为了提供较高效的template特定版本号。我们还是声明一个non-member swap但不再是std::swap的特化版本号或重载版本号:

    namespace WidgetStuff { 
        template<typename T> 
            class Widget{...}; 
        template<typename T> 
        void swap(Widget<T>& a, Widget<T>& b) { 
            a.swap(b); 
        } 
    }
    如今。不论什么地点的不论什么代码假设打算置换两个Widget对象,因而调用swap,C++的名称查找法则就是所谓“argument-dependent lookup”会找到WidgetStuff内的专属版本号。



    (三)

    template<typename T> 
    void doSomething(T& obj1, T& obj2) { 
        swap(obj1, obj2); 
    }
    上面的应该使用哪个swap?是std既有的那个一般化版本号还是某个可能存在的特化版本号等?你希望应该是调用T专属版本号。并在该版本号不存在的情况下调用std内的一般化版本号。以下是你希望发生的事:
    template<typename T> 
    void doSomething(T& obj1, T& obj2) { 
        using std::swap;    //令std::swap在此函数内可用 
        swap(obj1, obj2); 
    }
    c++的名称查找法则(name lookup rules)确保将找到global作用域或T所在之命名空间内的不论什么T专属的swap。假设T是Widget而且位于命名空间WidgetStuff内,编译器会找出WidgetStuff内的swap。

    假设没有T专属的swap存在。编译器就是用std内的swap,然而即便如此。编译器还是比較喜欢std::swap的T专属特化版本号,而非一般化的那个template。

    总结:

    (1)假设swap的缺省实现码对你的class或class template提供可接受的效率。那么我们不须要额外做不论什么事。

    (2)假设swap缺省实现版效率不足(某种pimpl),那么我们就像这样做:

    1.提供一个public swap成员函数。这个函数绝不该抛出异常。

    2.在class或template所在的命名空间内提供一个non-member swap, 并令他调用上述swap成员函数。

    3.假设正编写一个class(而非class template),为你的class特化std::swap。并令他调用swap成员函数。

    假设调用swap,确保包括一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加不论什么namespace修饰符,赤裸裸调用swap。

    swap的一个最好的应用是帮助classes(class templates)提供强烈的异常安全性保障。(条款29对此提供了全部细节)此技术基于一个如果:成员版的swap绝不抛出异常。当你写一个自定版本号的swap,提供的不仅仅是高效置换对象值的办法,并且不抛出异常。一般,这两个swap特性是连在一起的,由于高效的swaps差点儿总是基于对内置类型的操作(比如pimpl手法的底层指针),而内置类型上的操作绝不会抛出异常。


    请记住:

    (1)当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。

    (2)假设你提供一个member swap,也该提供一个non-member swap用来调用前者.对于classes(而非template),也请特化std::swap。


    (3)调用swap时应针对std::swap使用using声明式,然后调用swap而且不带不论什么"命名空间资格修饰“。
    (4)为"用户定义类型"进行std templates全特化是好的,但千万不要尝试在std内增加某些std而言全新的东西。

  • 相关阅读:
    Linux脚本文件注释
    Linux三剑客之grep命令
    Linux获取本机IP
    Linux的cut命令详解
    Linux的wc命令详解
    Linux的uniq命令详解
    Linux的sort命令详解
    shell之a+b求和脚本的三种写法
    shell的文件比较运算符和字符串比较运算符
    shell中变量$系列的含义
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5085053.html
Copyright © 2020-2023  润新知