【问题标题】:Copy Constructor in C++ [duplicate]C ++中的复制构造函数[重复]
【发布时间】:2015-07-15 06:09:43
【问题描述】:

这是一个我一直在问但无法得到明确答案的一般性问题。当这个类中的所有实例数据字段都是标准内置的 C++ 数据类型时,我是否需要为一个类编写一个复制构造函数?

【问题讨论】:

  • 您在使用new 吗?如果是,那么是的,您也需要复制构造函数和赋值运算符。
  • 也许吧。最常见的情况是,“标准内置 C++ 数据类型”之一是“指向 T 的指针”(对于某些类型 T),并且对象拥有指针指向的任何内容。

标签: c++


【解决方案1】:

成员变量的类型并不重要(1),它们的语义很重要。规则很简单:

如果您不提供复制构造函数,编译器将尝试为您生成一个。这个默认生成的将对所有成员变量执行默认复制操作。对于类类型,这意味着调用复制构造函数。对于原始类型,这意味着按位复制。

如果默认生成的构造函数满足您的需求,请不要声明您自己的构造函数。如果它不能满足您的需求,请自己声明。可以使用非原始成员变量和完美的默认复制语义创建一个类:

struct PersonId
{
  std::string surname;
  std::vector<std::string> givenNames;
};

同样,可以创建一个具有原始类型成员变量的类,其中默认复制语义可以:

class UniqueNamed
{
  int id;
  UniqueNamed() : id(0) {}

public:
  UniqueNamed(const UniqueNamed &src) : id(src.id + 1) {}

  int getId() const { return id; }

  static UniqueNamed initial;
};

所以它取决于类的语义,而不是其数据成员的类型。

这涉及到复制、移动和获胜语义的一般概念,以及它们在 C++ 中的实现。您可能想阅读有关rules of zero, three, and five 的内容。


(1) 当然,如果任何成员变量是不可复制的类型,并且您希望您的类是可复制的,您必须自己提供复制构造函数,作为默认值-声明的一个将被定义为已删除。

【讨论】:

    【解决方案2】:

    如果您不编写复制构造函数,则会创建一个默认值,它会逐一复制您类的每个字段,并调用它们的复制构造函数(如果有)。

    例如:

    class Test {
    public:
      int toto;
      char titi;
    };
    
    int main() {
        Test a;
    
        a.toto = 42;
        a.titi = 'a';
        Test b(a); // b will be initialized with same fields than a.
    
        return (0);
    }
    

    小心使用这种方法:只在简单的类上使用它,正如你所说,只有来自标准内置 C++ 数据类型的字段。

    这里最常见的错误是当你的类有一个指针字段时:指针被复制,但没有重新分配,所以你会有两个类的实例,它们的指针指向同一事物,如果其中一个修改或删掉,后果自负。

    class Test {
    public:
      std::string* field;
    
      Test() {
          field = new std::string("toto");
      }
      ~Test() {
          delete (field);
      }
    };
    
    int main () {
        Test a; // a.field is allocated.
    
        Test b(a); // b have the same pointer than a, as if you did b.field = a.field.
        // Here a and b destructors are called. They will delete the same pointers twice.
        // It will result as a segmentation fault.
        return (0);
    }
    

    注意= 运算符也是如此,如果你不重载它。

    【讨论】:

      【解决方案3】:

      如果你需要通过复制每个数据成员的值来复制对象,那么你不需要写一个;隐式生成的就是这样做的。

      如果您在复制对象时需要发生其他事情,或者如果某些事情阻止生成隐式对象(例如,const 数据成员)并且您仍然希望能够复制它,那么你需要写一个。

      【讨论】:

        【解决方案4】:

        一般来说,复制构造函数是由编译器为你创建的。仅当类中有特殊对象或指针,或者您希望进行特殊处理或定义特殊检查或其他特殊行为时,您才需要创建一个 / 和赋值运算符 /。 需要用户定义的复制构造函数的此类对象的示例是文件。 另一个需要用户定义的复制构造函数的例子是,默认的复制构造函数只会复制数据指针。这意味着它们将指向相同的对象/相同的内存块/。如果原始对象在堆栈上,当它超出范围时,析构函数将释放原始对象中的指针,并为您留下指向无效内存块的指针,您将遇到分段错误。

        【讨论】:

          【解决方案5】:

          不,如果您打算将其用作 POD(普通旧数据)对象,则不需要。编译器将为您生成默认的复制构造函数,其行为与您预期的一样 - 只需将所有字段从一个对象分配给另一个对象。

          【讨论】:

          • 如果类有指针成员怎么办?
          • @rozina,默认的复制构造函数将指针与其他字段相同——只是复制它们的值。在某些情况下,这是您需要的,在其他情况下……嗯,这就是您可以编写自己的构造函数的原因。
          • 所以你的回答是不正确的,因为它表明你不需要为 POD 类型编写自己的复制构造函数:)
          • @rozina,据我所知,只要类具有用户定义的复制构造函数,它就不能是 POD 类。所以我真的没有看到我的错误。也许我应该澄清当你想要“非默认”行为(尤其是指针)时的情况,但这意味着我没有得到正确的问题。因为我认为 OP 会问如果一个人不编写自己的复制 ctor 而不是与此相关的问题会发生什么。
          猜你喜欢
          • 1970-01-01
          • 2018-10-17
          • 2015-08-09
          • 2014-01-19
          • 2012-02-04
          • 2023-03-11
          • 1970-01-01
          • 2013-12-19
          • 1970-01-01
          相关资源
          最近更新 更多