C++ 拷贝构造函数语义与移动构造函数语义

一、拷贝构造函数语义

拷贝构造函数用于复制对象。当一个对象被复制时,编译器会根据特定条件生成默认的拷贝构造函数。

1. 编译器生成拷贝构造函数的条件
  1. 类中有类类型成员且没有自定义拷贝构造函数

    • 示例

      class CTB {
      public:
          CTB() {}
          CTB(const CTB& other) { /* 复制逻辑 */ }
      };
      
      class A {
      public:
          CTB member; // CTB类型成员
      };
      
      // A的拷贝构造函数会自动生成
      A a1;
      A a2 = a1; // 调用A的拷贝构造函数
      
  2. 子类没有自定义拷贝构造函数且父类有拷贝构造函数

    • 示例

      class CTB {
      public:
          CTB() {}
          CTB(const CTB& other) { /* 复制逻辑 */ }
      };
      
      class CTBSon : public CTB {
      public:
          // 没有自定义拷贝构造函数
      };
      
      // CTBSon的拷贝构造函数会自动生成
      CTBSon son1;
      CTBSon son2 = son1; // 调用CTBSon的拷贝构造函数
      
  3. 子类定义了虚函数

    • 示例

      class Base {
      public:
          virtual void func() {}
      };
      
      class CTBSon : public Base {
      public:
          // 没有自定义拷贝构造函数
      };
      
      // CTBSon的拷贝构造函数会自动生成
      CTBSon son1;
      CTBSon son2 = son1; // 调用CTBSon的拷贝构造函数
      
  4. 类中含有虚基类

    • 示例

      class VirtualBase {
      public:
          VirtualBase() {}
          VirtualBase(const VirtualBase& other) { /* 复制逻辑 */ }
      };
      
      class A : public VirtualBase {
      public:
          // 没有自定义拷贝构造函数
      };
      
      // A的拷贝构造函数会自动生成
      A a1;
      A a2 = a1; // 调用A的拷贝构造函数
      
2. 自定义拷贝构造函数

用户可以自定义拷贝构造函数以实现特定的复制逻辑。例如,可以选择深拷贝或浅拷贝。

class MyClass {
public:
    int* data;
    MyClass(int value) : data(new int(value)) {}
    
    // 自定义拷贝构造函数
    MyClass(const MyClass& other) {
        data = new int(*other.data); // 深拷贝
    }
    
    ~MyClass() {
        delete data;
    }
};
3. 深拷贝与浅拷贝
  • 浅拷贝:仅复制指针的值,两个对象指向同一内存地址,可能导致悬空指针或双重释放的问题。

    • 示例

      class ShallowCopy {
      public:
          int* data;
          ShallowCopy(int value) : data(new int(value)) {}
          
          // 浅拷贝的构造函数
          ShallowCopy(const ShallowCopy& other) : data(other.data) {}
          
          ~ShallowCopy() {
              delete data; // 可能导致双重释放
          }
      };
      
  • 深拷贝:复制指针所指向的数据,确保每个对象都有自己的独立副本。

4. 拷贝构造函数的使用场景
  • 对象传递:在函数参数中传递对象时,通常会调用拷贝构造函数。
  • 返回对象:当函数返回一个对象时,拷贝构造函数会被调用。
5. 拷贝赋值运算符

拷贝构造函数和拷贝赋值运算符的逻辑通常相似,但它们的使用场景不同。拷贝赋值运算符用于将一个对象的值赋给另一个已存在的对象。

6. 规则五(Rule of Five)

如果自定义了拷贝构造函数,通常也需要自定义移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数,以确保资源管理的正确性。确保它们的实现不冲突,并且正确管理资源。


二、移动构造函数语义

移动构造函数用于转移对象的资源。编译器生成移动构造函数的条件更加严格。

1. 禁止生成移动构造函数
  • 条件:如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或析构函数(任意一个),则编译器不会为该类生成移动构造函数和移动赋值运算符。

  • 示例

    class MyClass {
    public:
        MyClass() {}
        MyClass(const MyClass& other) { /* 复制逻辑 */ } // 自定义拷贝构造函数
    };
    
    // MyClass不会生成移动构造函数
    MyClass obj1;
    MyClass obj2 = std::move(obj1); // 错误:没有移动构造函数
    
2. 生成移动构造函数的条件
  • 条件:只有在类未定义任何自定义版本的拷贝构造函数、拷贝赋值运算符和析构函数时,且类的每个非静态成员都可以移动,编译器才会为该类合成移动构造函数和移动赋值运算符。

  • 示例

    class Movable {
    public:
        Movable() {}
        Movable(Movable&& other) noexcept { /* 移动逻辑 */ }
    };
    
    class MyClass {
    public:
        Movable member; // 可移动
        // 没有自定义拷贝构造函数、拷贝赋值运算符和析构函数
    };
    
    // MyClass会生成移动构造函数
    MyClass obj1;
    MyClass obj2 = std::move(obj1); // 正常调用移动构造函数
    
3. 成员可以移动的定义
  • 内置类型:如整型、浮点型等的成员变量可以移动。

  • 类类型:如果成员变量是一个类类型,并且该类有对应的移动构造函数或移动赋值运算符,则该成员变量可以移动。

  • 示例

    class Movable {
    public:
        Movable() {}
        Movable(Movable&& other) noexcept { /* 移动逻辑 */ }
    };
    
    class NonMovable {
    public:
        NonMovable() {}
        NonMovable(const NonMovable& other) { /* 复制逻辑 */ }
    };
    
    class MyClass {
    public:
        Movable movableMember; // 可移动
        NonMovable nonMovableMember; // 不可移动
        // 由于存在不可移动的成员,MyClass不会生成移动构造函数
    };
    
    // MyClass不会生成移动构造函数
    MyClass obj1;
    MyClass obj2 = std::move(obj1); // 错误:没有移动构造函数
    

三、总结

  • 拷贝构造函数主要用于复制对象,编译器会在特定条件下自动生成,确保对象的成员正确复制。
  • 移动构造函数用于转移对象的资源,编译器的生成条件更为严格,主要依赖于类是否有自定义的拷贝相关函数和成员的可移动性。

四、补充内容

1. 拷贝与移动的区别
  • 拷贝:创建一个对象的副本,所有成员都被复制,原对象和副本相互独立。
  • 移动:转移资源的所有权,原对象的资源被转移到新对象,原对象处于有效但未定义的状态。
2. 使用场景
  • 拷贝构造函数适用于需要保留原对象状态的情况,例如在需要多个相同对象的场景。
  • 移动构造函数适用于临时对象或资源管理,能够减少不必要的内存开销,提高性能。
3. 实践建议
  • 尽量使用移动语义以提高性能,尤其是在处理大型对象或动态分配资源时。
  • 在自定义类时,如果定义了拷贝构造函数,考虑是否也需要定义移动构造函数,以便在需要时能够高效地转移资源。
  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值