这是一个一般性问题,我已经问了一段时间,但无法得到明确答案。当此类中的所有实例数据字段都是在C ++数据类型中标准构建时,是否需要为类编写复制构造函数?
答案 0 :(得分:5)
成员变量的类型对于(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;
};
所以它取决于类的语义,而不是它的数据成员的类型。
这触及了复制,移动和放大的语义的一般概念。 winrship,以及它们在C ++中的实现。您可能想要阅读有关rules of zero, three, and five。
的内容(1)当然,如果任何成员变量属于不可复制类型,并且您希望您的类可以复制,则必须自己提供复制构造函数,作为默认值 - 声明一个将被定义为已删除。
答案 1 :(得分:2)
如果您需要通过复制每个数据成员的值来复制对象,那么您不需要编写一个;隐式生成的那个就完全可以了。
如果在复制对象时需要执行其他操作,或者某些事情阻止生成隐式文件(例如,const
数据成员)并且您仍希望能够复制它,那么你需要写一个。
答案 2 :(得分:1)
如果您不编写复制构造函数的代码,则会创建一个默认的复制构造函数,逐个复制您的类的每个字段,如果有的话,调用它们的复制构造函数。
例如:
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 :(得分:1)
通常,复制构造函数是由编译器为您创建的。只有当类中有特殊对象或指针,或者您希望进行特殊处理或定义特殊检查或其他特殊行为时,才需要创建一个/和分配运算符/。 需要用户定义的复制构造函数的此类对象的示例是文件。 您需要用户定义的复制构造函数的其他示例是默认的只复制数据指针。这意味着它们将指向相同的对象/相同的内存块/。如果原始对象在堆栈上,当它超出范围时,析构函数将释放原始对象中的指针,并且将为您指向指向无效内存块的指针,并且您将出现分段错误。
答案 4 :(得分:-2)
不,如果您打算将其用作POD(普通旧数据)对象,则不要这样做。编译器将为您生成默认的复制构造函数,其行为与您期望的一样 - 只需将所有字段从一个对象分配给另一个对象。