【C++ Primer】 第十三章 拷贝控制_13.3 交换操作

  1. 除了定义拷贝控制成员,资源管理的类通常还定义一个名为swap的函数。(哪些类会调用swap的函数:那些与重排元素顺序的算法一起使用的类)。
  2. 如果一个类定义了自己的swap,那么算法将使用类自定义版本。否则,算法将使用标准库定义的swap。

编写我们自己的swap函数

  1. 类定义一个自己版本的swap来重载swap的默认行为。
  2. 与拷贝控制成员不同,swap并不是必要的。但是,对于分配了资源的类,定义swap可能是一种很重要的优化手段。
class HasPtr{
    //swap定义为frined,以便能访问HasPtr的(private的)数据成员
    friend void swap(HasPtr&,HasPtr&);
    //其他成员定义
};
//swap的存在就是为了优化代码,将其声明为inline函数
inline void swap(HasPtr &lhs,HasPtr &rhs)
{
    /*
     swap的函数体对给定对象的每个数据成员调用swap。
     在本例中,数据成员(ps,i)是内置类型的,而内置类型是没有特定版本的swap,
    所以在本例中,对swap的调用会调用标准库std::swap。
    */
    using std::swap;
    swap(lhs.ps,rhs.ps);//交换指针,而不是string数据
    swap(lhs.i,rhs.i);//交换int成员
}

swap 函数应该调用swap,而不是std::swap

        如果一个类的成员有自己类型特定的swap函数,调用std::swap就是错误的了。

/*
假设Foo类有一个类型为HasPtr的成员h。
如果我们未定义Foo版本的swap,就会使用标准库版本的swap。
如例1,标准库swap对HasPtr管理的string进行了不必要的拷贝。
*/
//例1:会编译通过,但不建议这么写
void swap(Foo &lhs,Foo &rhs)
{
    //错误:这个函数使用了标准库版本的swap,而不是HasPtr版本
    std::swap(lhs.h,rhs.h);//标准库swap对HasPtr管理的string进行了不必要的拷贝。
    //交换类型Foo的其他成员
}

/*
每个swap调用应该都是未加限定的。即
每个调用都应该是swap,而不是std::swap.
如果存在类型特定的swap版本,其匹配程度会优于std中定义的版本。

*/
//例2:正确的swap函数
void swap(Foo &lhs,Foo &rhs)
{
    using std::swap;
    swap(lhs.h,rhs.h);//使用HasPtr版本的swap
    //交换类型Foo的其他成员
}

在赋值运算符中使用swap

  1.  定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了一种名为拷贝并交换的技术。这种技术将左侧运算对象与右侧运算对象的一个副本进行交换。
  2. 拷贝并交换技术自动处理了自赋值情况且天然就是异常安全的。
/*
注意rhs是按值传递的,意味着HasPtr的拷贝构造函数将右侧运算对象中的string拷贝到rhs
*/
HasPtr& HasPtr::operator=(HasPtr rhs)//非引用参数,右侧运算对象以传值方式传递给赋值运算符。
{
    //交换左侧运算对象和局部变量rhs的内容
    swap(*this,rhs);//rhs现在指向本对象曾经使用的内存
    return *this;//rhs被销毁,HasPtr的析构函数将执行,从而delete了rhs中的指针,即释放掉左侧运算对象中原来的内存
}

#include<iostream>
#include<cstring>
using namespace std;
class HasPtr {
    friend void swap(HasPtr&, HasPtr&);
public:
    /*
    第一个构造函数接受一个(可选的)string参数。
    这个构造函数动态分配它自己的string副本。并将指向string的指针保存在ps中。
    */
    HasPtr(const string& s = string()) :ps(new string(s)), i(0) {}//第一个构造函数
    //对于ps指向的string,每个HasPtr对象都有自己的拷贝
    HasPtr(const HasPtr& p) :ps(new string(*p.ps)), i(p.i) { }//拷贝构造函数也分配它自己的string副本
    HasPtr& operator=(const HasPtr&);//拷贝赋值运算符
    HasPtr& operator= (const string&);//赋予新string
    string& operator*();//解引用
    ~HasPtr();//析构函数对指针成员ps执行delete,释放析构函数中分配的内存。
private:
    string* ps;
    int i;
};

//swap的存在就是为了优化代码,将其声明为inline函数
inline void swap(HasPtr& lhs, HasPtr& rhs)
{
    /*
     swap的函数体对给定对象的每个数据成员调用swap。
     在本例中,数据成员(ps,i)是内置类型的,而内置类型是没有特定版本的swap,
    所以在本例中,对swap的调用会调用标准库std::swap。
    */
    cout << "交换" << *lhs.ps << "和" << *rhs.ps << endl;
    swap(lhs.ps, rhs.ps);//交换指针,而不是string数据
    swap(lhs.i, rhs.i);//交换int成员
}

HasPtr::~HasPtr()
{
    delete ps;//释放string内存
}
inline HasPtr& HasPtr::operator=(const HasPtr& rhs)
{
    if (this == &rhs)//检测自我赋值  myadd
        return *this;
    auto newps = new string(*rhs.ps);//拷贝指针指向的对象
    delete ps; //销毁原string
    ps = newps;//指向新string
    i = rhs.i;//使用内置的int赋值
    return *this;//返回一个此对象的引用
}
HasPtr& HasPtr::operator=(const string& rhs)
{
    *ps = rhs;
    return *this;
}
string& HasPtr::operator*()
{
    return *ps;
}
int main(int argc, char** argv)
{
    HasPtr h("hi,mom!");
    /*
    如果未定义拷贝构造函数,在拷贝HasPtr对象时,合成的拷贝构造函数会建档复制ps成员,使得两个HasPtr指向相同的string。
    当其中一个HasPtr修改string内容时,另一个HasPtr也被改变,这不符合我们的设想。
    如果同时定义了析构函数,情况会更为糟糕,当销毁其中一个HasPtr时,ps指向的strig被销毁,另一个HasPtr的ps成为空悬指针。
    */
    HasPtr h2(h);//调用拷贝构造函数;行为类值,h2,h3和h指向不同string
    HasPtr h3 = h;//调用拷贝构造函数
    h2 = "hi,dad!";
    h3 = "hi,son!";
    swap(h2,h3);
    cout << "h:" << *h << endl;
    cout << "h2:" << *h2 << endl;
    cout << "h3:" << *h3 << endl;
    return 0;
}

/*关于vector调用拷贝构造函数的原理*/
#include <iostream>
#include <cstdlib>
#include <vector>
using namespace std;
class Point
{
public:
    Point()
    {
        cout << "construction" << endl;
    }
    Point(const Point& p)
    {
        cout << "copy construction" << endl;
    }
    ~Point()
    {
        cout << "destruction" << endl;
    }
};
int main()
{
    vector<Point> pointVec;
    Point a;   //创建a会调用一次构造函数,释放时调用析构函数
    Point b;   //创建b会调用一次构造函数,释放时调用析构函数
    pointVec.push_back(a);//vector会申请一个内存空间,并调用拷贝构造函数将a放到vector中
    pointVec.push_back(b);//此时内存不够 ,需要扩大内存,重新分配内存,这时再调用拷贝构造函数将a拷贝到新的内存,再将b拷入新的内存,同时又调用Point拷贝构造函数,最后释放原来a的内存,此时调用Point的析构函数。
    cout << pointVec.size() << std::endl;
    //程序结束,pointVec包含两个元素a和b,故还会调用两次析构函数。
    return 0;
}

============================================================

/************当main函数中n不超过16时,swap并未被调用**************/
#include<iostream>
#include<string>
#include <vector>
#include<algorithm>
//using namespace std;
using std::string;
using std::cout;
using std::endl;
using std::vector;
using std::to_string;
class HasPtr {
    friend void swap(HasPtr&, HasPtr&);
public:
    /*
    第一个构造函数接受一个(可选的)string参数。
    这个构造函数动态分配它自己的string副本。并将指向string的指针保存在ps中。
    */
    HasPtr(const string& s = string()) :ps(new string(s)), i(0) { cout <<  "construction" << endl; }//第一个构造函数
    //对于ps指向的string,每个HasPtr对象都有自己的拷贝
    HasPtr(const HasPtr& p) :ps(new string(*p.ps)), i(p.i) { cout << "copy  construction" << endl; }//拷贝构造函数也分配它自己的string副本
    HasPtr& operator=(const HasPtr&);//拷贝赋值运算符
    HasPtr& operator= (const string&);//赋予新string
    string& operator*();//解引用
    bool operator <(const HasPtr&) const;//比较运算
    ~HasPtr();//析构函数对指针成员ps执行delete,释放析构函数中分配的内存。
private:
    string* ps;
    int i;
};
HasPtr::~HasPtr()
{
    cout << "destruction" << endl;
    delete ps;//释放string内存
}
inline HasPtr& HasPtr::operator=(const HasPtr& rhs)
{
    auto newps = new string(*rhs.ps);//拷贝指针指向的对象
    delete ps; //销毁原string
    ps = newps;//指向新string
    i = rhs.i;//使用内置的int赋值
    return *this;//返回一个此对象的引用
}
HasPtr& HasPtr::operator=(const string& rhs)
{
    *ps = rhs;
    return *this;
}
string& HasPtr::operator*()
{
    return *ps;
}
//swap的存在就是为了优化代码,将其声明为inline函数
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
    /*
     swap的函数体对给定对象的每个数据成员调用swap。
     在本例中,数据成员(ps,i)是内置类型的,而内置类型是没有特定版本的swap,
    所以在本例中,对swap的调用会调用标准库std::swap。
    */
    using std::swap;
    cout << "交换" << *lhs.ps << "和" << *rhs.ps << endl;
    swap(lhs.ps, rhs.ps);//交换指针,而不是string数据
    swap(lhs.i, rhs.i);//交换int成员
}
bool HasPtr::operator <(const HasPtr& rhs) const
{
    return *ps < *rhs.ps;
}
int main(int argc, char** argv)
{
    vector<HasPtr>vh;
    int n = atoi("2");//把字符串转换成整型数
    for (int i = 0; i < n; i++)
        vh.push_back(to_string(n - i));//调用1次构造函数(创建新对象)、1次拷贝构造函数和1次析构函数;再调用1次构造函数(创建新对象)和2次拷贝构造函数,最后调用2次析构函数(n=2时)
    for (auto p : vh)//会调用两次拷贝构造函数:原理是从vh中取一个内容赋值给变量p;然后在循环体中操作p;每循环一遍,执行一次析构函数(n=2时)
        cout << *p << " ";
    cout << endl;
    // sort(首元素地址(必填),尾元素地址的下一个地址(必填),比较函数(非必填));如果不写比较函数,则默认对前面给出的区间进行递增排序。
    sort(vh.begin(),vh.end());//先调用一次拷贝构造函数,再调用两次比较运算函数,再调用两次拷贝赋值运算符,最后再调用一次析构函数(n=2时)
    for (auto p : vh)//调用两次拷贝构造函数,每循环一遍,执行一次析构函数(n=2时)
        cout << *p << " ";
    cout << endl;
    return 0;
    //程序结束,会再调用两次析构函数
}

/*********当main函数中n大于等于17时,经调试,swap也并未被调用,与答案不符***********/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷凝女子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值