zoukankan      html  css  js  c++  java
  • 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的缩写)。设计Widget class:

    class WidgetImpl
    {
    public:
        ...
    protected:
    private:
        int a, b, c;
        std::vector<double> v;
        ...
    };
    class Widget
    {
    public:
        Widget(const Widget& rhs);
        Widget& operator=(const Widget& rhs)
        {
            ...
            *pImpl = *(rhs.pImpl);
        }
    protected:
    private:
        WidgetImpl* pImpl;
    };

    要置换两个Widget对象值,唯一要做的就是置换pImpl指针,缺省的swap算法不知道这一点。不只复制3个Widget还复制3个WidgetImpl对象。非常缺乏效率!

    我们可以将std::swap全特化:

    namespace std{

        template<>

        void swap<Widget>(Widget& a, Widget& b)

        {

            swap(a.pImpl, b.pImpl);//不能通过编译,访问private成员变量

        }

    }

    虽然可以把这个特化版本声明为friend,但这和以往的规矩不太一样,我们可以令Widget声明一个swap的public成员函数做真正的替换工作,然后将std::swap特化,令他调用该成员函数:

    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);
        }
    protected:
    private:
        WidgetImpl* pImpl;
    };


    namespace std{
        template<>
        void swap<Widget>(Widget& a, Widget& b)
        {
            a.swap(b);
        }
    }

    假设Widget和WidgetImpl都是class templates而非classes:

    template <typename T>

    class WidgetImpl{…};

    template <typename T>

    class Widget{…};

    在特化std::swap时:

    namespace std{
        template<typename T>
        void swap<Widget<T>>(Widget<T>& a, Widget<T>& b) //wrong!不合法!
        {
            a.swap(b);
        }
    }

    我们企图偏特化一个function template,但是c++中只允许对class templates偏特化,在function template身上偏特化是行不通的。这段代码不该通过编译(虽然有些编译器错误地接受了它)。

    当打算偏特化一个function template时,惯常的做法是简单的为它添加一个重载版本:

    namespace std{
        template<typename T>
        void swap(Widget<T>& a, Widget<T>& b) //注意“swap后没有<>”
        {
            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。

    std::swap(obj1,obj2); //这是错误的swap调用方式

    这便强迫编译器只认std内的swap(包括其任何template特化),因此不再调用一个定义于他处的较适当T专属版本。那正是“你的classes对std::swap进行全特化的”重要原因:使得类型专属的swap实现版本可以被这些迷途代码所用。

    如果swap的缺省实现码对你的classes或class template提供可接受的效率,不需要额外做任何事。

    如果swap缺省实现版效率不足(某种pimpl):

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

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

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

    如果调用swap,确保包含一个using声明式,然后不加任何namespace修饰符,赤裸裸调用swap。

    成员版swap绝不可抛出异常

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

  • 相关阅读:
    【css】如何实现响应式布局
    【PHP】foreach语法
    【css】cursor鼠标指针光标样式知识整理
    【JavaScript】修改图片src属性切换图片
    【PHP】PHP中的排序函数sort、asort、rsort、krsort、ksort区别分析
    【PHP】常用的PHP正则表达式收集整理
    【Mysql】mysql中bigint、int、mediumint、smallint 和 tinyint的取值范围
    js获取url参数的方法
    SQL Server 2008 geometry 数据类型
    SQL Server 存储过程之基础知识(转)
  • 原文地址:https://www.cnblogs.com/lidan/p/2328364.html
Copyright © 2011-2022 走看看