六一的部落格


关关难过关关过,前路漫漫亦灿灿。



与拷贝赋值的语义有关

  1. 行为像值的类

    类值

    使用拷贝赋值运算符时, 副本和原对象相互独立

    如string

  2. 行为像指针的类

    类指针

    使用拷贝赋值运算符赋值后, 副本和原对象使用相同的底层数据

    如shared_ptr和initializer_list

  3. 其他

    如unique_ptr

如果一个类需要自定义析构函数, 几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数


类值: 拷贝赋值运算符, 拷贝构造函数与析构函数

 1class HasPtr
 2{
 3public:
 4    HasPtr(const string &s = string()) : ps(new string(s)), i(0) { }
 5    HasPtr(const HasPtr &p) : ps(new string(*p.ps), i(p.i)) { }
 6    HasPtr &operator=(const HasPtr &);
 7    ~HasPtr() { delete ps; }
 8
 9private:
10    string *ps;
11    int i;
12};
13
14HasPtr &HasPtr::operator=(const HasPtr &rhs)
15{
16    auto newp = new string(*rhs.ps);
17    delete ps;
18    ps = newp;
19    i = rhs.i;
20
21    return *this;
22}

拷贝赋值运算符

  1. 通常组合了析构函数和构造函数的操作:
    • 销毁左侧运算对象
    • 拷贝右侧运算对象
  2. 要保证将对象赋予它自身时, 能正确工作

安全性说明

  1. 申请动态内存可能会失败: 需保证该异常不会影响到原对象

  2. 右侧运算对象和 *this 可能为同一个对象: 读取完毕后销毁左侧运算对象

 1HasPtr &HasPtr::operator=(const HasPtr &rhs)
 2{
 3    auto newp = new string(*rhs.ps);
 4
 5    delete ps;
 6    ps = newp;
 7
 8    i = rhs.i;
 9
10    return *this;
11}

类指针: 拷贝赋值运算符, 拷贝构造函数与析构函数

可以帮助理解shared_ptr

 1class HasPtr
 2{
 3public:
 4    HasPtr(const string &s = string()) : ps(new string(s)), i(0), use(new size_t(1)) { }
 5    HasPtr(const HasPtr &p) : ps(new string(*p.ps), i(p.i)), use(p.use) { ++*use; }
 6    HasPtr &operator=(const HasPtr &);
 7    ~HasPtr();
 8
 9private:
10    string *ps;
11    int i;
12    size_t *use;
13};
14
15HasPtr::~HasPtr()
16{
17    if (--*use == 0)
18    {
19        delete ps;
20        delete use;
21    }
22}
23
24HasPtr &HasPtr::operator=(const HasPtr &rhs)
25{
26    ++*rhs.use;
27
28    if (--*use == 0)
29    {
30        delete ps;
31        delete use;
32    }
33
34    ps = rhs.ps;
35    use = rhs.use;
36    i = rhs.i;
37
38    return *this;
39}
40
41int main()
42{
43    HasPtr p1("Hiya!");
44    HasPtr p2(p1);
45    HasPtr p3(p1);
46    return 0;
47}

引用计数的实现

将计数器保存在动态内存中

基类与派生类共享静态数据成员


拷贝赋值运算符

自赋值处理没有问题, 一次递增一次递减抵消了


拷贝赋值运算符行为: 类值和类指针


与拷贝赋值的语义有关

  1. 行为像值的类

    类值

    使用拷贝赋值运算符时, 副本和原对象相互独立

    如string

  2. 行为像指针的类

    类指针

    使用拷贝赋值运算符赋值后, 副本和原对象使用相同的底层数据

    如shared_ptr和initializer_list

  3. 其他

    如unique_ptr

如果一个类需要自定义析构函数, 几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数


类值: 拷贝赋值运算符, 拷贝构造函数与析构函数

 1class HasPtr
 2{
 3public:
 4    HasPtr(const string &s = string()) : ps(new string(s)), i(0) { }
 5    HasPtr(const HasPtr &p) : ps(new string(*p.ps), i(p.i)) { }
 6    HasPtr &operator=(const HasPtr &);
 7    ~HasPtr() { delete ps; }
 8
 9private:
10    string *ps;
11    int i;
12};
13
14HasPtr &HasPtr::operator=(const HasPtr &rhs)
15{
16    auto newp = new string(*rhs.ps);
17    delete ps;
18    ps = newp;
19    i = rhs.i;
20
21    return *this;
22}

拷贝赋值运算符

  1. 通常组合了析构函数和构造函数的操作:
    • 销毁左侧运算对象
    • 拷贝右侧运算对象
  2. 要保证将对象赋予它自身时, 能正确工作

安全性说明

  1. 申请动态内存可能会失败: 需保证该异常不会影响到原对象

  2. 右侧运算对象和 *this 可能为同一个对象: 读取完毕后销毁左侧运算对象

 1HasPtr &HasPtr::operator=(const HasPtr &rhs)
 2{
 3    auto newp = new string(*rhs.ps);
 4
 5    delete ps;
 6    ps = newp;
 7
 8    i = rhs.i;
 9
10    return *this;
11}

类指针: 拷贝赋值运算符, 拷贝构造函数与析构函数

可以帮助理解shared_ptr

 1class HasPtr
 2{
 3public:
 4    HasPtr(const string &s = string()) : ps(new string(s)), i(0), use(new size_t(1)) { }
 5    HasPtr(const HasPtr &p) : ps(new string(*p.ps), i(p.i)), use(p.use) { ++*use; }
 6    HasPtr &operator=(const HasPtr &);
 7    ~HasPtr();
 8
 9private:
10    string *ps;
11    int i;
12    size_t *use;
13};
14
15HasPtr::~HasPtr()
16{
17    if (--*use == 0)
18    {
19        delete ps;
20        delete use;
21    }
22}
23
24HasPtr &HasPtr::operator=(const HasPtr &rhs)
25{
26    ++*rhs.use;
27
28    if (--*use == 0)
29    {
30        delete ps;
31        delete use;
32    }
33
34    ps = rhs.ps;
35    use = rhs.use;
36    i = rhs.i;
37
38    return *this;
39}
40
41int main()
42{
43    HasPtr p1("Hiya!");
44    HasPtr p2(p1);
45    HasPtr p3(p1);
46    return 0;
47}

引用计数的实现

将计数器保存在动态内存中

基类与派生类共享静态数据成员


拷贝赋值运算符

自赋值处理没有问题, 一次递增一次递减抵消了