考虑写出一个不抛异常的swap函数-创新互联

目录

成都创新互联公司长期为上千余家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为黎城企业提供专业的成都网站制作、成都网站设计,黎城网站改版等技术服务。拥有10余年丰富建站经验和众多成功案例,为您定制开发。

一.标准库中的swap函数

二.针对于非模板类,设计全特化的std::swap()

三.针对模板类

四.合理使用using

五.swap成员函数不能抛出异常

六.swap函数总结


一.标准库中的swap函数

在C++11中有move函数,它可以是一个左值变为右值,在许多场景下可以提高效率。可以参考这篇博客:

C++中move的使用_真爱是蓝色的博客-博客_c++ move

C++11介绍_"派派"的博客-博客

补充:

C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
1.如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个 。那么编译器会自动生成一个默认移动构造。 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
2.如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。 ( 默认移动赋值跟上面移动构造完全类似 )

在一些自定义类型的数据上,如果没有移动构造,移动赋值的话,在某些场景下,那么该如何用swap提高效率呢?

二.针对于非模板类,设计全特化的std::swap()

某些情况下,并不适合用std::swap()。

例如:

例如:

widget w1;
widget w2;
std::swap(w1,w2);

一旦要置换两个widget对象值,我们唯一需要做的就是置换其pImpl指针,但缺省的swap算法不知道这一点。它不只复制三个widgets,还复制三个 widgetImpl对象。

使用全特化的std::swap()函数,例如:

namespace std {
    //template<>用于表示这是一个std::Swap的全特化版本
    template<>void swap(Widget& a, Widget& b) 
    {
        //错误的,pImpl是private的,无法编译通过
        swap(a.pImpl, b.pImpl);
    }
}

但pImpl是private的,因此函数无法编译通过,解决办法:为类设计一个swap成员函数,并设计一个全局swap函数,或者将上面那个特化swap设置为friend(不推荐),例如:

class Widget
{
public:
    void swap(Widget& rhs)
    {
        using std::swap;        
        swap(pImpl, rhs.pImpl); //调用std::swap()函数,只需交换两个对象的指针
    }
private:
    WidgetImpl* pImpl;
};
 
namespace std {
    template<>void swap(Widget& a, Widget& b) 
    {
        a.swap(b); //调用Widget::swap()成员函数
    }
}

补充:上面我们是在std中偏特化了std::swap()函数,但是我们不建议这样做,因为这样可能会对std命名空间造成污染,一种解决解决方法是在std命名空间之外定义全特化swap()函数。

三.针对模板类

如果上面的两个类是模板类呢?例如:

templateclass WidgetImpl { };
 
templateclass Widget { };

C++只允许对类模板偏特化,不允许对函数模板偏特化,所以下面的代码是无法编译通过的,例如:

namespace std {
    //此处是错误的,C++只允许对类模板偏特化,不允许对函数模板偏特化
    templatevoid swap>(Widget& a, Widget& b)
    {
        a.swap(b);
    }
}

那么该如何解决呢?可以使用重载版本代替偏特化,例如:

templateclass WidgetImpl{ //同上 };
 
templateclass Widget{ //同上 };
 
namespace std{
    //这里std::swap的一个重载版本,而不是特化版本,swap后没有<....>templatevoid swap(Widget& a, Widget& b)
    {
        a.swap(b);
    }
}

上面我们重载了std::swap函数,但是是在std命名空间中进行重载的,std是个特殊的命名空间,其管理规则比较特殊,因此我们不建议在std中重载任何内容

一种解决方法就是在自己的命名空间中定义swap()函数。例如:

补充:

1.我们在自己的命名空间中定义的swap()函数不属于std::swap()的重载版本,因为它们作用域不一致
2.此处我们以命名空间为例,其实不使用命名空间也可以,我们主要是为了指出不要与std::swap()产生冲突。但是为了让全局数据空间太过杂乱,我们建议使用命名空间
3.不要在std中进行全特化。因此处的方法不仅适用于模板类,同样也适用于非模板类

四.合理使用using

假设此时我们编写了一个函数模板,其接受两个参数,并在模板内调换两个元素。代码如下:

templatevoid doSomething(T& obj1,T& obj2)
{
    //...
    swap(obj1,obj2);  //调用哪一个版本的swap()函数哪?
    //...
}

应该调用哪个swap?是std既有的那个一般化版本?还是某个可能存在的特化版本?抑或是一个可能存在的T专属版本而且可能栖身于某个命名空间(但当然不可以是std)内?你希望的应该是调用T专属版本,并在该版本不存在的情况下调用std内的一般化版本。

合理做法:

若果是这样使用呢?

这便强迫编译器只认std内的swap(包括其任何template特化),因而不再可能调用一个定义于它处的较适当T专属版本。若在std中特化了也行。

五.swap成员函数不能抛出异常

成员本的swap函数绝不可能抛出异常。因为swap的一个最好的应用是帮助类(和类模板)提供强烈的异常安全性保障(条款29介绍)
但此技术基于一个假设:成员版的swap绝不抛出异常。这一约束只施加于swap成员版本,不可施加于非成员版本,因为swap缺省版本是以拷贝构造函数和拷贝赋值运算符为基础,而一般情况下两者都允许抛出异常
因此当你写下一个自定义的swap,往往提供的不只是高效置换对象的版本,而且还不抛出异常

六.swap函数总结

1.当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异     常
2.如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于       class(而非template class),也请特化std::swap
3.调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间n    资格修饰”
4.用“用户定义类型”进行std template全特化时最好的,但千万不要尝试在std内加入某些对    std而言全新的东西

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前名称:考虑写出一个不抛异常的swap函数-创新互联
网站路径:http://hbruida.cn/article/dccsss.html