Copy Constructor in C++
这是一个一般性的问题,我已经问了一段时间,但没有得到明确的答案。当这个类中的所有实例数据字段都是标准的C++数据类型时,是否需要为类编写一个复制构造函数?
成员变量的类型对此(1)并不重要,它们的语义是。规则很简单:
如果不提供复制构造函数,编译器将尝试为您生成一个。这个默认生成的操作将对所有成员变量执行默认的复制操作。对于类类型,这意味着调用复制构造函数。对于基元类型,这意味着按位复制。
如果默认生成的构造函数满足您的需要,则不要声明自己的构造函数。如果它不能满足你的需要,你自己声明一个。可以使用非基元成员变量和完全正常的默认复制语义创建类:
1 2 3 4 5 | struct PersonId { std::string surname; std::vector<std::string> givenNames; }; |
同样,可以使用基本类型成员变量创建一个类,其中默认的复制语义将不正常:
1 2 3 4 5 6 7 8 9 10 11 12 | 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++中的实现。你可能想读一些关于零、三和五的规则。
(1)当然,如果任何成员变量是不可复制的类型,并且您希望类是可复制的,那么您必须自己提供复制构造函数,因为默认声明的构造函数将被定义为删除。
如果您需要通过复制每个数据成员的值来复制对象,那么您不需要编写一个;隐式生成的对象将完全做到这一点。
如果您在复制对象时需要发生其他事情,或者如果某些事情阻止了隐式对象的生成(例如,
通常,复制构造函数是由编译器为您创建的。只有当类中有特殊对象或指针,或者希望进行特殊处理,或者定义特殊检查或其他特殊行为时,才需要创建一个/和分配运算符。例如,需要用户定义的复制构造函数的对象是一个文件。另一个需要用户定义的复制构造函数的例子是,默认的复制构造函数只复制数据指针。这意味着它们将指向相同的对象/相同的内存块/。如果原始对象在堆栈上,当它超出范围时,析构函数将释放原始对象中的指针,并将为您留下指向无效内存块的指针,您将出现分段错误。
如果不编写复制构造函数的代码,则会创建一个默认值,该值会逐个复制类中的每个字段,如果有的话,会调用它们的复制构造函数。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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++中的标准数据类型的字段。
这里最常见的错误是当你的类有一个指针字段时:指针被复制了,但没有重新分配,所以你的类有两个实例,一个指针在同一个对象上,如果其中一个修改或删除了它,另一个会感觉到后果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); } |
注意:对于
不,如果要将其用作pod(普通旧数据)对象,则不需要。编译器将为您生成默认的复制构造函数,其行为如您所期望的那样——只需将一个对象中的所有字段分配给另一个对象。