• EC读书笔记系列之13:条款25 考虑写出一个不抛异常的swap函数


    记住:

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

    ★若你提供一个member swap,也该提供一个non-member swap来调用前者。对于classes(而非templates),也请特化std::swap

    ★调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”

    ★为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西

    --------------------------------------------------------------------------------------------

    swap函数的背景:

        原本其只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值(Copy-and-swap技术)可能性的一个常见机制。

    举例:

    class Widget { 
    
        public:
            Widget( const Widget &rhs );
            Widget& operator=( const Widget &rhs ) {
                //复制Widget时令其复制其WidgetImpl对象
                ...
                *pImpl = *( ths.pImpl );
                ...
            }
            ...
        private:
            WidgetImpl *pImpl;
    };
    其中的WidgetImpl类如下:
    class WidgetImpl { public: ... private: int a, b, c; std::vector<double> v; //可能有许多数据,意味着复制时间很长 };

    若要置换两Widget对象值,仅需置换其pImpl指针,但STL缺省的swap并不知这样操作,它不只复制三个Widget,还复制三个WidgetImpl对象,非常缺乏效率!!!

    做法一(编译出错):

    namespace std {
    
        template<>  //这是std::swap针对T是Widget的特化版
        void swap<Widget>( Widget &a, Widget &b ) {
        
            swap( a.pImpl, b.pImpl );  //仅需置换指针就好
        }
    }

    template<>表示它是std::swap的一个全特化版(不能够改变std命名空间内的任何东西,但可以为标准templates制造特化版,使他专属于自己的classes,如本例的Widget,上面的代码就是这样操作的)。上述代码有问题,因为pImpl是private,∴swap( a.pImpl, b.pImpl );语句出错

    做法二(针对的是class的情况):

    class Widget {
    
        public:
            ...
            void swap( Widget &other ) { //在Widget内增加swap成员函数
            
                using std::swap; //此声明必要
                swap( pImpl, other.pImpl ); //置换的是指针
            }
        ...
    };
    
    namespace std {
    
        template<> //修订后的std::swap特化版本
        void swap<Widget>( Widget &a, Widget &b ) {
        
            a.swap(b); //若要置换Widget,调用其swap成员函数
        }
    }

    此做法可以,而且还与STL容器有一致性,∵所有STL容器也都提供有public swap成员函数和std::swap特化版本(用来调用前者)。

    做法三(编译出错):

    上述做法是可以,但针对的是Widget和WidgetImpl是class的情况,假设Widget和WidgetImpl都是class template该如何呢

      template<typename T> //现在变为class template

      class WidgetImpl {};

      template<typename T>  //现在变为class template了

      class Widget {};

    在Widget内放swap成员跟做法二是一样,但特化std::swap时会遇到问题

    namespace std {
    
        template<typename T>     //这是偏特化
        void swap< Widget<T> >( Widget<T> &a, Widget<T> &b ) { //错误!
        
            a.swap(b);
        }
    }

    这段所谓的特化代码通不过编译,∵我们企图偏特化一个function template(std::swap),但C++只允许对class template偏特化,不能对function template这么搞。

    做法四:做法三的解决办法

     声明一个non-mem. swap让它调用mem. swap,但不再将那个non-mem. swap声明为std::swap的特化版。

    namespace WidgetStuff {
    
        ...                     //模板化的WidgetImpl等等
        template<typename T>
        class Widget {...};  //同前,内含swap成员函数
        ...
        template<typename T> //单独写一个non-member !!不要特化了!
        void swap( Widget<T> &a, Widget<T> &b ) { //注意这里并不属std空间
        
            a.swap(b);
        }
    }

    总结:

    目前已经讨论default swap,member swap,non-member swap,std::swap的特化版本。

      首先,若swap的缺省实现码对你的class或class template提供可接受的效率,无需做额外的事情;

      其次,若swap缺省实现版效率不足,试着做以下事情:

        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(即做法二Widget类中所写的swap函数)绝不可抛出异常!!!

  • 相关阅读:
    openlayers 聚合效果
    OpenLayers 3 实现轨迹回放
    经纬度和墨卡托互相转换
    OpenLayers 3 给features 添加手势
    OpenLayers 3 实现划线,画点
    C# 在窗体的子线程中创建新窗体
    openlayers 3 读取展示shp文件
    地理信息未来5年规划
    OpenLayers3 实现自定义放大缩小滑块,自定义方向按钮
    2014最后一篇,记ExpandableListViewd的自定义
  • 原文地址:https://www.cnblogs.com/hansonwang99/p/4948948.html
Copyright © 2020-2023  润新知