16

可能重复:
我们什么时候必须使用复制构造函数?

为什么 C++ 复制构造函数如此重要?我刚刚了解了他们,我不太明白他们有什么大惊小怪的。如果您使用指针,似乎您应该始终为您的类编写一个复制构造函数,但为什么呢?

谢谢,博达赛多。

4

5 回答 5

30

复制构造函数和赋值运算符在 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++ 中很容易错误地复制额外的副本,因为该语言是围绕复制的概念构建的。

一个黄金法则是,如果你的类有一个析构函数(因为它需要做一些清理),那么很可能一个成员一个成员的副本不是正确的事情......而且如果你有特殊的逻辑来做复制构造,那么在赋值中也可能需要类似的逻辑(反之亦然)。因此,被称为“三巨头”的规则规定,要么你的类没有自定义析构函数、没有复制构造函数、没有赋值运算符,要么你的类应该拥有这三个。

这条规则非常重要,例如,如果对于任何特殊情况,你最终得到一个只需要一个析构函数的类(我认为这是一个不合理的情况......但我们只是说你找到了一个)那么请记住添加为一个你考虑过的评论,你知道隐式生成的复制构造函数和赋值运算符是可以的。如果你不添加关于其他两个的注释,任何阅读你的代码的人都会认为你只是忘记了它们。

更新

C++ 正在发展,虽然这里所说的大部分内容现在仍然有效,但该语言提供了一种更好的方法来通知编译器不应允许复制和赋值。

新语法(自 C++11 起有效)是

struct Window {
    ...
    Window(const Window&) = delete;
    Window& operator=(const Window&) = delete;
};
于 2010-07-31T15:17:38.197 回答
7

很简单:当 C++ 使用默认的复制构造函数时,它会复制指针,但不会复制指向的数据。结果:两个对象指向相同的数据。如果两者都认为自己拥有该数据,并在调用其析构函数时删除指针,那么您将遇到很多麻烦...

于 2010-07-31T14:45:03.200 回答
7

为什么 C++ 复制构造函数如此重要?

大多数其他语言不需要复制构造函数,因为它们:

  • 没有指针(例如,旧版本的 BASIC),在这种情况下复制对象总是安全的,或者
  • 只有指针/引用(例如 Java、Python),在这种情况下复制很少见,然后可以使用copy()orclone()方法完成。

C++ 更喜欢值语义,但也使用大量指针,这意味着:

  • 对象被大量复制,并且
  • 由于其他人提到的原因,您必须指定是要浅拷贝还是深拷贝。
于 2010-07-31T15:29:11.140 回答
4

C++ 中的每个类都有一个隐式复制构造函数,它执行对象的浅拷贝。浅意味着它复制成员的值。因此,如果有一个指针,则指针值被复制,因此两个对象都指向同一个。

大多数时候这是不需要的,因此您必须定义自己的复制构造函数。

通常您甚至不想创建对象的副本,因此声明一个复制构造函数但不定义它是一种很好的风格。(头文件中的空声明)。

然后,如果您不小心创建了一个副本(例如,返回一个对象,在声明参数引用方法时忘记了 & 等),您将收到链接器错误。

如果您将复制构造函数声明为私有,您还会收到编译器错误(如果在类外使用)。

总结一下:显式声明复制构造函数总是一种很好的方式——尤其是如果你根本不需要 on 的话:只要写

private:
  MyClass(const MyClass&);

在您的类定义中禁用您的类的复制构造函数。

于 2010-07-31T15:13:25.053 回答
1

深拷贝和浅拷贝

于 2010-07-31T14:54:36.480 回答