C++为什么要提供复制构造函数?赋值运算符可以完成相同的任务。复制构造函数与赋值运算符相比有什么优点吗?
C++为什么要提供复制构造函数?赋值运算符可以完成相同的任务。复制构造函数与赋值运算符相比有什么优点吗?
拷贝构造函数可以实现的操作,而赋值运算符则很难或者根本无法实现的操作:
拷贝那些没有默认构造函数的类。例如,如果一个类表示打开的文件,你可能需要传递文件名才能构造出一个该类的实例。
拷贝那些拥有昂贵默认构造函数的类。也许构造函数会分配大量内存,但当你使用赋值运算符将新的状态拷贝到对象中时,这些内存就会被释放。
通过值传递类的实例。这也是拷贝构造函数最初的目的。在C语言中,如果你通过值传递一个结构体,编译器只会对该结构体进行位拷贝,以便接收函数拥有一个本地副本,并且可以修改它而不影响调用者。但是C++意识到对大多数对象进行位拷贝并不是最好的拷贝方式,因此它允许你编写自己的拷贝构造函数(并且默认的拷贝行为也不同,因为类成员可能拥有自定义的拷贝构造函数)。
复制一个包含引用的类,因为在类已经构造后你无法重新分配引用。复制构造函数和赋值运算符对于引用有不同的作用。复制构造函数将引用初始化为指向被复制实例中引用指向的相同对象;赋值运算符则实际上复制了被引用对象的值。
复制一个拥有const成员的类。(注意:一个类可以有默认构造函数但仍然有const成员。)
无论有没有复制构造函数,您仍然需要将新对象初始化为稳定的初始状态,这样赋值运算符可以稍后更新它。
虽然您可以在没有复制构造函数的情况下完成此操作,但拥有复制构造函数有助于优化新对象的初始化,通过一开始将其设置为复制另一个对象的状态,而无需先将新对象初始化为默认状态,然后再进行单独的赋值以重置该状态。这样,您可以通过1次操作设置新对象的状态,而不是2次操作。
是的,这两者是不同的。您不能总是只实现您的复制构造函数为
Foo(const Foo& f) {
*this = f;
}
赋值运算符假设您拥有一个有效的、完全构造的对象。而复制构造函数则没有这样的假设。这意味着,根据您的类,赋值运算符可能会尝试清除对象上的任何数据以便重新初始化。或者甚至可能重新使用对象上已经存在的数据。
*this = f;
类型的构造函数。然而,我无法通过复制构造实现赋值。问题是这个问题是否有一些习惯用法的解决方案。 - Evg拿这个例子来说。
杰克和约翰是双胞胎。 你可以说这个是杰克,那个是杰克。 但尽管约翰长得和杰克一模一样,他并不是杰克。
当你使用赋值运算符时,你可以引用内存中的确切对象(返回*this),或提供一些自定义行为。
当你使用复制构造函数时,你想在内存中创建另一个对象,该对象具有原始对象的属性副本,但可以朝着不同的方向发展。
如果你想要更深入的答案,我认为这篇博客文章更好。
赋值比初始化要棘手一些,因为我们实际上正在解构现有对象,然后使用新值重新构造它。在更复杂的类中,您可能需要释放各种资源,然后使用从要复制的对象中复制的资源副本重新分配它们。std::unique_ptr 在这里使我们的生活变得容易,因为将 std::unique_ptr 的新实例分配给使用 =(如上所述)或使用 reset() 的现有 std::unique_ptr 对象首先释放旧指针,因此自动处理释放资源(这是我们的类不需要析构函数的相同原因 - 当 std::unique_ptr 超出范围时,我们的对象超出范围,std::unique_ptr 的析构函数被调用并自动释放资源)。
Foo copy(source);
操作? - NathanOliverFoo copy(source);
和Foo copy; copy = source;
的区别/优劣。前者会创建一个新的对象并将其初始化为source,而后者需要先创建一个空对象,然后再将其复制为source。前者更加简洁直观,同时可以避免未初始化的情况,但是如果类有自定义的拷贝构造函数或赋值运算符重载,这两种方法可能会产生不同的结果。 - Remy Lebeau