我是否违反了“三大法则”?

3

最近我读到了C++编程中的“三法则”(Rule of three),我想知道我是否违反了它?

在我的GUI应用程序中,像MainFrameInterfaceCircuitBreadboard等类(类名是指示性的)每个都只有一个实例。在它们的构造函数中,我分配了一些资源(内存),并在其析构函数中安全释放。

因此,我仅定义了析构函数,但没有定义复制构造函数赋值运算符

我确定我不需要它们,但我很好奇是否违反了这条规则,我该怎么做才能遵循它?

5个回答

6

三法则是关于处理“大三”(构造函数、拷贝构造函数和赋值运算符)的规定,但这不一定意味着您必须定义它们。您可以提供它们或禁止它们,但不能忽略它们。


所以我只定义了析构函数,但没有定义拷贝构造函数和拷贝赋值运算符。
我有违反三法则吗?

是的,您违反了该规则。编译器将生成拷贝构造函数和拷贝赋值运算符,由于您在构造函数中分配内存并在析构函数中释放内存,因此这些副本将具有错误的语义:它们将复制指针,导致两个类别别名相同的内存。赋值甚至不会释放旧内存,而只是覆盖指针。

这是个问题吗?

如果像您所暗示的那样,您不对这些类的实例进行复制或分配操作,则不会出错。但最好保险起见,将拷贝构造函数和拷贝赋值运算符声明为私有的(并不用定义),这样您就不会不小心调用它们。

在C++11中,您可以使用= delete语法来实现:

T(T const&) = delete; // no copy constructor
T& operator=(T const&) = delete; // no copy assignment

第一条语句解决了我的疑惑。= delete看起来是一个好的解决方案,只要告诉我这两个原型是否也应该被标记为私有的(除了= delete)。 - Vinayak Garg
@Vinayak:如果你使用= delete,那么它们是公有的还是私有的就无关紧要了。 - R. Martinho Fernandes
如果调用者使用它们,编译器将生成一个复制构造函数和复制赋值运算符。当代码没有使用时是否生成代码的问题通常是无关紧要的,但如果默认副本不可能(例如,如果类具有const或引用非静态数据成员),那么这也足以防止意外——如果有人尝试复制,则编译器将无法生成该函数,否则它甚至不会尝试。个人而言,我不会仅依赖于这一点,我还会删除它们,以使读者清楚地知道这是有意的。 - Steve Jessop

2

您应该声明(但不实现)一个私有的复制构造函数和赋值运算符。确保不要实现这些函数。这将防止任何不应被复制的类的复制。


2
不要空函数。你应该将它们声明为私有的,且不指定任何实现。如果你的代码最终复制/赋值对象,这是注定会失败的。 - StilesCrisis
请关注 @StilesCrisis 的问题。请相应地编辑您的答案。 - Vinayak Garg

2
这很大程度上取决于您的应用逻辑以及您如何向用户记录接口类。通常,优秀的C++程序员必须了解三个规则(如果您知道“复制并交换惯用语”,则为三个半)以及在C++11中有5个半(移动语义)。如果您的类管理资源,并且同一类可复制(即复制构造函数和赋值运算符未定义为私有),则编写自己的复制构造函数和赋值运算符进行深层复制非常重要。但是,如果您总是通过引用传递类,则最好将默认复制构造函数和赋值运算符定义为私有,以便即使您错误地按值传递或复制,编译器也会警告您。请保留HTML标记。

1

如果你不需要它,就不要遵循它。三法则的背后动机是,当你需要一个析构函数时,通常是因为你需要进行一些动态分配。

如果你也进行了分配,那么你还需要复制构造函数和赋值运算符。想象一下,你有一个指向某个东西的类:

struct Foo
{
    Foo() { ptr_ = new int; }
    ~Foo() { delete ptr_; }
    int* ptr_;
};

因为你没有定义一个复制构造函数和赋值操作符,所以每当你复制一个Foo对象时,原始对象和副本都会使用指向同一个int的指针;当其中一个对象被销毁时,指针被释放,导致另一个对象无法使用该数据。
Foo(cont Foo& other) {
    other.ptr_ = new int(*ptr_);
}

// Same for operator=

如果在构造函数/析构函数中没有进行任何动态分配,那么很有可能您实际上不需要复制构造函数或赋值运算符(但不一定)。

1

是的,根据该定义,它确实违反了三大原则。

然而,这只是一个"经验法则"。一个通用的指导方针。如果你不需要复制构造或赋值操作,就不要实现它们。其他人建议将它们声明为私有并定义为空。我会更进一步说,甚至不要定义它们。

如果你定义了它们,那么你仍然可能调用空方法。相反,将它们留未定义,并且如果你尝试调用这些方法,你将收到链接错误,因为找不到方法的定义。在编译时错误优于运行时错误/不希望的行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接