C++:48---交换操作(为类设置一个swap函数)

class HasPtr {
public:
    HasPtr(const std::string &s = std::string())
        :ps(new std::string(s)), i(0) {}
    
    HasPtr(const HasPtr& p)  //拷贝构造函数
        :ps(new std::string(*(p.ps))), i(p.i) {}
 
    HasPtr& operator=(const HasPtr& rhs); //拷贝辅助运算符
 
    ~HasPtr() { delete ps; }
private:
    std::string *ps;
    int i;
};

//我们这个operator=可以处理自我赋值的情况
HasPtr& HasPtr::operator=(const HasPtr& rhs)
{
    auto newp = new std::string(*(rhs.ps));
    delete ps; //释放旧内存
    ps = newp; //使用新内存
    i = rhs.i;
    return *this; //返回自身
}

一、为什么要设计swap操作

  • 对于上面的HasPtr类,如果我们希望把两个类进行交换操作,那么一般我们会执行如下的代码:
HasPtr v1, v2;

HasPtr temp = v1; //创建一个临时变量保存v1
v1 = v2;          //将v2赋值给v1
v2 = temp;        //将原v1赋值给v2
  • 在上面的代码代码会执行一次拷贝和两次赋值:
    • 拷贝:将v1拷贝给temp
    • 将v2赋值给v1,将temo赋值给v2
  • 对于上面的拷贝与赋值操作,都会在内部执行string对象的创建。这些内存的分配是不必要的,我们只是希望交换swap的指针,而不用分配string的新副本。因此,理论上对于上面v1和v2的交换操作,我们只交换ps指针就可以了:
//下面是伪代码,因为ps是private的
HasPtr v1, v2;

string *temp = v1.ps;
v1.ps = v2.ps;
v2.ps = temp;

二、编写自己的swap函数

  • 现在我们编写一个自定义版本的swap函数,实现如下:
class HasPtr {
public:
    //其他代码省略(同上)
    friend void swap(HasPtr&, HasPtr&);
};

inline void swap(HasPtr &lhs, HasPtr &rhs)
{
    using std::swap;
    swap(lhs.ps, lhs.ps);//交换指针,而不是string数据
    swap(lhs.i, lhs.i);  //交换int成员
}
  • 函数格式讲解:
    • friend:因为ps和i都是私有成员,因此需要将其定义为friend函数
    • inline:为了优化代码
  • 与拷贝控制成员不同,swap不是必须要的。但是,对于分配了资源的类,定义swap可能是一种很重要的手段

三、调用自定义的swap,而不是std::swap

  • 标准模板库std也定义了一个swpa函数,因此调用自定义的swap函数的时候需要防止与std::swap冲突
  • 现在假设有一个Foo类,其中有一个HasPtr类
class HasPtr {};

class Foo
{
public:
    HasPtr ptr;
};

void swap(Foo &lhs, Foo &rhs)
{
    std::swap(lhs.ptr, rhs.ptr); //此处调用的是std::swap,没有调用我们自定义的swpa函数
}
  • 因此我们需要修改代码,使其调用我们上面自定义的swap函数
void swap(Foo &lhs, Foo &rhs)
{
    using std::swap;
    swap(lhs.ptr, rhs.ptr); //使用HasPtr版本的swap函数
}

四、在赋值运算符中使用swap

  • 定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了一种名为拷贝并交换的技术
  • 代码如下:
class HasPtr {
public:
    //其他代码同上
    HasPtr& operator=(HasPtr rhs);
};

HasPtr& HasPtr::operator=(HasPtr rhs)
{
    //交换左侧对象和局部变量rhs的内容
    swap(*this, rhs); //rhs现在只想本对象增使用的内存
    return *this;
}//函数结束后,rhs析构,释放rhs中的指针(本对象原来使用的)
  • 这个技术的又去之处在于:
    • 它自动处理了自我赋值的情况且天然就是一场安全的
    • 它通过在改变左侧运算对象之前拷贝右侧多想保证了自我赋值的正确,这与我们在原来的赋值运算符中使用的方法一致
    • 它保证一场安全的方法也与原来的赋值运算实现一样
    • 代码中唯一可能抛出异常的是拷贝构造函数中的new表达式。如果真的发生了异常,它也会在我们改变左侧对象之前发生
  • 使用拷贝和交换的赋值运算符自动就是异常安全的,且能正确处理自我赋值
  • 这一类型的运算符我们将在后面一篇文章也会讲解:https://blog.csdn.net/qq_41453285/article/details/104419356
发布了1481 篇原创文章 · 获赞 1026 · 访问量 38万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104426758