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


    1. swap是STL的一部分,后来成为异常安全性编程(exception-safe programming)(见条款29)的一个重要脊柱,标准库的swap函数模板定义类似以下:

    namespace std{
        template<typename T>
        swap(T& lhs,T& rhs){
            T temp(lhs);
            lhs=rhs;
            rhs=temp;
        }
    }
    View Code

    只要T类型支持拷贝构造以及拷贝赋值,标准库swap函数就会调用T的拷贝构造函数和拷贝构造操作符完成值的转换,但对于某些类,这种默认的转换方式代价太大,比如:

    class Demo{
    public:
        Demo(const Demo&);
        Demo& operator=(const Demo& rhs){
            ...
            *ptr=*rhs.ptr;
            ...
        }
        ...
    private:
        vector<int>* ptr;
    }
    View Code

    如果按照标准库swap的默认行为所付出的的代价很大,而实际上只要置换两个指针的地址即可.

    2. 解决1的一种方法是对标准库std命名空间内的swap函数模板进行特化,如下:

    namespace std{
        template<>
        void swap<Demo>(Demo& lhs,Demo& rhs){
            swap(lhs.ptr,rhs.ptr);
        }
    }
    View Code

    通常我们不能改变标准库std命名空间内的东西,但是C++允许为标准templates制造特化版本,因此以上代码理论上来说是可行的,但由于Demo的ptr成员被设为private,所以以上代码通不过编译.我们可以为Demo声明一个名为swap的public成员函数执行相关操作,然后令标准库特化swap调用该函数,如下:

    class Demo{
    public:
        ...
        void swap(Demo& rhs){
            using std::swap;
            swap(ptr,rhs.ptr);
        }
        ...
    }
    namespace std{
        void swap(Demo& lhs,Demo& rhs){
            lhs.swap(rhs);
        }
    }
    View Code

    这种做法不但可以通过编译,而且与STL保持一致,因为"所有STL容器也都提供有public成员函数和std::swap特化版本"

    如果Demo是class template,那么情况就要发生变化,例如:

    template<typename T>
    class Demo{ ... }

    如果要采用以上方法,在Demo类模板内定义一个swap成员函数是可行的,但是要特化std::swap时势必要这样:

    namespace std{
        template<typename T>
        void swap<Demo<T> >(Demo<T>& lhs,Demo<T>& rhs){
            lhs.swap(rhs);
        }
    }
    View Code

    结果通不过编译,因为C++标准目前只允许对class templates偏特化,而不允许对function templates偏特化(注:偏特化指的是将一个模板特化为另一个模板,例如部分类型参数特化以及将类型参数特化为容器类型参数(如上))

    另一个方法是为std::swap添加一个重载版本,如下:

    namespace std{
        template<typename T>
        void swap(Demo<T>& lhs,Demo<T>& rhs){//注意没有swap之后<>,所以这是重载不是特化
            lhs.swap(rhs);
        }
    }
    View Code

    不幸的是这也编译不通过,因为C++虽然允许对标准库templates进行特化,"但不可以添加新的templates(或classes或functions或其他任何东西)"到std里。

    还有一种方法,与以上相同,声明一个non-member swap让它调用member swap,但不再将那个non-member声明为std::swap的特化或重载版本,而是置于另一个命名空间DemoStuff内,如下:

    namespace DemoStuff{
        ...
        template<typename T>
        class Demo{...}
        ...
        template<typename T>
        void swap(Demo<T>& lhs,Demo<T>& rhs){
            lhs.swap(rhs);
        }
    }
    View Code

    当然,以上也可以声明在全局命名空间内,但是这样可能会造成作用域的杂乱无章(用《Effective C++中文版》来说,要保持"得体与适度").

     3. 假设对于以下函数:

    template<typename T>
    void doSomething{T& lhs,T& rhs){
        ...
        swap(lhs,rhs);
        ...
    }
    View Code

    如果想要调用T的专属swap版本,并在该笨笨不存在的情况下,调用std内的一般化版本,可以按以下定义:

    template<typename T>
    void doSomething{T& lhs,T& rhs){
        using std::swap;
        ...
        swap(lhs,rhs);
        ...
    }
    View Code

    "一旦编译器看到对swap的调用,它们便寻找适当的swap并调用之.C++的名称查找法则(name lookup roles)确保将找到global作用域或T所在之命名空间内的任何T专属的swap."如果T是Demo并未与DemoStuff命名空间内,编译器会使用"实参取决之查找规则"(argument-dependent lookup)找出DemoStuff之内的swap."如果没有T专属之swap存在,编译器就调用std内的swap".(using std::swap并没有指定出现的swap是std内的版本,而是使std内的swap在函数内可见,如果按"std::swap(obj1,obj2)"的方式调用,调用的必是std内的版本)

    3. 成员版(指的是以上的高效率版)swap绝不可抛出异常!正如以上所言,swap的一个重要应用是"帮助classes(或class templates)提供强烈的异常安全性(exception-safety)保障",但这一技术只适用于成员版(高效率版),因为默认版本的swap要调用拷贝构造函数和拷贝赋值操作符,而这两种函数都允许抛出异常.因此对于自定义的高效版本往往提供的不只是高效置换值的方法,而且是不抛出异常."一般而言这两种swap特性是连在一起的,因为高效率的swap总是基于对内置类型的操作,而内置类型上的操作绝不会抛出异常".

  • 相关阅读:
    如何解决C#异常:必须先将当前线程设置为单线程单元(STA)模式,然后才能进行OLE调用,请确保你的Main函数已在其上标记了STAThreadAttribute
    go多态
    go泛型
    protoc工具使用
    grpc protobuf协议
    grpc根据proto文件自动生成go源码
    go安装grpc
    go protobuf
    go读取http.Request中body的内容
    go数据库操作
  • 原文地址:https://www.cnblogs.com/reasno/p/4771255.html
Copyright © 2020-2023  润新知