swap function & copy-and-swap idiom

在C++中,一个资源管理类(例如含有指向堆内存的指针)中需要重新定义拷贝构造函数、赋值运算符以及析构函数(Big Three),在新标准下还可能需要定义移动构造函数和移动赋值预算法(Big Five)。但实际上,这条规则还可以有一个小拓展。就是在资源管理类中,往往需要重新定义自己的swap函数来作为优化手段。

1.Swap函数

首先考察如下例子,假设类HashPtr中含有一个指向string的指针*ps和一个int类型value。

class HasPtr {
    public:
        ...
        ...
        ...
    private:
        string* ps;
        int value;
};

则加入没有定义自己的swap函数的话,调用标准库的std::swap相当于进行如下动作:

HasPtr temp = v1;
v1 = v2;
v2 = temp;

这里v1中的string拷贝了两次,造成不必要的效率上的浪费。
其实我们知道,这里swap只需要交换指针指向就可以,所以写出如下自定义版本的swap函数:

class HasPtr {
    friend void swap(HasPtr& lhs, HasPtr& rhs);
    ...
    ...
};

inline
void swap(HasPtr& lhs, HasPtr& rhs) {
    using std::swap;
    swap(lhs.ps, rhs,ps);
    swap(lhs.value, rhs.value);
}

注意: 这里有一点值得注意的是,函数调用的时候应该写成swap(可以先声明std::swap),而非直接用std::swap形式。这是因为假如有另一个类型Foo含有成员h(HasPtr类型),则如果HasPtr定义了自己的swap函数的话,应该调用其自定义的swap函数,否则调用标准库的std::swap。

所以如下形式1是不对的,应该写成形式2 。二者执行结果相同,但效率不同(前者不必要的拷贝)。

//形式1
void swap(Foo& lhs, Foo& rhs) {
    std::swap(lhs.h, rhs.h);    //直接用标准库的swap
}
//形式2
void swap(Foo & lhs, Foo & rhs) {
    using std::swap;
    swap(lhs.h, rhs.h);        //用的是HasPtr的swap
}

2.copy and swap idiom

定义了swap函数的类常用swap来定义赋值运算发,即copy and swap技术。

  • 好处:天然的异常安全并且正确处理自我赋值问题, 是简洁高效安全的写法。(参考effective c++ tip 11)

上述例子使用copy and swap技术定义赋值运算符如下:

HasPtr& HasPtr::operator=(HasPtr rhs) {    //直接传值
    swap(*this, rhs);
    return *this;
}

可以看出,不同于一般的赋值运算符,其参数采用传值方式,而非引用。它与如下代码功能上等价,但效率上更高(将copy动作移至函数参数构造阶段)

HasPtr& HasPtr::operator=(const HasPtr& rhs) {      //传引用
    HasPtr temp(rhs);        //还需要构造一个temp
    swap(*this, temp);
    return *this;
}

这两种方式均可以完美解决异常安全和自我赋值问题。第一种方法被C++ primer和C++编程规范所推荐,Scott Meyers在effective c++中表达了一些对其可读性的忧虑,但如果掌握好copy and swap idiom,还是应该推荐采用第一种写法生成更高效的代码。

参考关于swap and copy idiom问题在stackoverflow上的解答

你可能感兴趣的:(swap function & copy-and-swap idiom)