为什么C ++拷贝构造器确实如此重要?我刚刚了解了它们,我不太清楚它们是什么。如果您使用指针,似乎应该总是为您的类编写一个复制构造函数,但为什么?
谢谢,Boda Cydo。
答案 0 :(得分:29)
复制构造函数和赋值运算符在C ++中非常重要,因为该语言具有“复制语义”,也就是说当您传递参数或在容器中存储值时,传递或存储对象的副本。 C ++如何在对象上复制或执行赋值?对于本机类型,它本身就知道,但对于用户定义的类型,它会自动生成逐个成员的副本构造或赋值。
如果您声明例如:
,则更明确class P2d
{
public:
double x, y;
P2d(double x, double y) : x(x), y(y)
{ }
};
C ++编译器自动完成您的代码
class P2d
{
public:
double x, y;
P2d(double x, double y) : x(x), y(y)
{ }
P2d(const P2d& other) : x(other.x), y(other.y)
{ }
P2d& operator=(const P2d& other)
{
x = other.x;
y = other.y;
return *this;
}
};
这些自动生成的复制构造函数和赋值运算符是否适用于您的类?在许多情况下是的...但当然可能这些实现完全错误。通常情况下,例如当你的对象中包含指针时,只需要复制指针就可以制作对象的副本了。
您必须了解C ++会执行大量对象副本,并且您必须了解它将为您定义的类执行哪种类型的副本。如果自动生成的副本不是您需要的,那么您必须提供自己的实现,或者您必须告诉编译器应该禁止您的类复制。
您可以通过声明私有复制构造函数和赋值运算符以及不提供实现来阻止编译器进行复制。因为那些是私有函数,任何将要使用它们的外部代码都会出现编译器错误,并且因为你声明了它们但是你没有实现它们,如果你错了,你会得到链接错误最终在类实现中制作副本。
例如:
class Window
{
public:
WindowType t;
Window *parent,
*prev_in_parent, *next_in_parent,
*first_children, *last_children;
Window(Window *parent, WindowType t);
~Window();
private:
// TABOO! - declared but not implemented
Window(const Window&); // = delete in C++11
Window& operator=(const Window&); // = delete in C++11
};
如果最后一部分看起来很荒谬(你怎么能错误地在实现中复制)请注意,在C ++中,错误地制作额外的副本非常容易,因为语言是围绕复制事物的概念构建的
一个黄金法则是,如果你的类有一个析构函数(因为它需要做一些清理),那么很可能一个成员的副本不是正确的做法...而且如果你有特殊的逻辑为了进行复制构造,在分配时也可能需要类似的逻辑(反之亦然)。因此,称为 Big Three 的规则声明,您的类没有自定义析构函数,没有复制构造函数,没有赋值运算符,或者您的类应该包含所有这三个。
这条规则非常重要,例如,如果对于任何特殊情况,你最终会得到一个只需要一个析构函数的类(我不能认为是一个明智的案例......但是我只是说你找到了一个)然后请记住添加你想到它的注释,你知道隐式生成的复制构造函数和赋值运算符是可以的。 如果你没有添加关于其他两个的注释,那么任何阅读你代码的人都会认为你只是忘记了它们。
C ++正在发展,虽然这里所说的大部分内容仍然有效,但现在语言提供了一种更好的方法来通知编译器不应该允许复制和赋值。
新语法(自C ++ 11起有效)
struct Window {
...
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
};
答案 1 :(得分:7)
简单:当C ++使用默认的复制构造函数时,它会复制指针,但不指向的数据。结果:两个对象指向相同的数据。如果他们都认为他们拥有这些数据,并在调用析构函数时删除指针,那么你就会遇到麻烦的堆 ......
答案 2 :(得分:6)
为什么C ++拷贝构造函数确切 这么重要吗?
大多数其他语言不需要复制构造函数,因为它们可以:
copy()
或clone()
方法完成C ++更喜欢值语义,但也使用了很多指针,这意味着:
答案 3 :(得分:4)
C ++中的每个类都有一个隐式复制构造函数,用于执行对象的浅表复制。浅表示它复制成员的值。因此,如果有一个指针,则指针值被复制,因此两个对象都指向同一个。
大多数情况下这不是必需的,因此您必须定义自己的复制构造函数。
通常,您甚至不想创建对象的副本,因此声明复制构造函数是一种很好的方式,但不能定义它。 (头文件中的空声明)。
然后,如果你意外地创建了一个副本(例如,返回一个对象,忘记了&当声明一个逐个参数的方法等)时,你会得到一个链接器错误。
如果将复制构造函数声明为private,则还会出现编译器错误(如果在类外使用)。
总结一下:明确声明复制构造函数总是很好的风格 - 特别是如果你根本不需要的话:只需写一下
private:
MyClass(const MyClass&);
在类定义中禁用类的复制构造函数。
答案 4 :(得分:1)