提问人:Vinayak Garg 提问时间:2/4/2012 最后编辑:Vinayak Garg 更新时间:2/4/2012 访问量:811
我是否违反了三法则?
Am I violating Rule of three?
问:
我最近读了《三法则》,想知道我是否违反了它?
在我的 GUI 应用程序中,像 、 、 等类(类名是指示性的)每个类都有一个实例。在他们的构造函数中,我分配了一些资源(内存),我在他们的析构函数中安全地释放了这些资源。MainFrame
Interface
Circuit
Breadboard
所以我只定义了析构函数,但没有定义复制构造函数和赋值运算符。
我确定我不需要它们,但我很好奇我是否违反了规则,我可以/应该做些什么来遵守它?
答:
三法则是关于处理所有三巨头,但这并不一定意味着如果你不想定义它们,就必须定义它们。要么你提供它们,要么你禁止它们。你不应该做的是忽略它们。
所以我只定义了析构函数,但没有定义复制构造函数和复制运算符。
我是否违反了三法则?
是的,你违反了规则。编译器将生成一个复制构造函数和复制赋值运算符,并且由于您在构造函数中分配内存并在析构函数中释放,因此这些副本将具有错误的语义:它们将复制指针,并且您将有两个类别名相同的内存。该赋值甚至不会释放旧内存,而只是覆盖指针。
这是个问题吗?
如果像您暗示的那样,您不制作副本或分配给这些类的实例,则不会出错。但是,最好是安全起见,将复制构造函数和复制赋值运算符声明为私有(甚至不要费心定义),这样就不会意外调用它们。
在 C++11 中,您可以改用以下语法:= delete
T(T const&) = delete; // no copy constructor
T& operator=(T const&) = delete; // no copy assignment
评论
= delete
= delete
)
= delete
const
您应该声明(但不实现)私有副本构造函数和赋值运算符。确保您没有实现这些函数。这将防止对不应复制的类进行任何形式的复制。
评论
是的,根据该定义,它确实违反了三法则。
然而,这是一条“经验法则”。一般准则。如果不需要复制构造或赋值操作,请不要实现它们。其他人则建议将它们声明为私有并将它们定义为空。我会更进一步说,甚至不要定义它们。
如果定义了它们,则仍可能调用空方法。相反,请将它们保留为未定义状态,如果尝试调用这些方法,将收到链接器错误,因为找不到方法定义。优先于构建时错误,而不是运行时错误/意外行为。
这很大程度上取决于您的应用程序逻辑以及您如何向用户记录您的接口类。
通常,一个好的 c++ 程序员必须知道 3 法则(如果你知道“复制和交换习惯法”,则为 5 和 1/2,如果是 c++11(移动语义)。
如果你的类管理资源,并且同一个类是可复制的(即复制 ctor 和 assigment 运算符未定义为私有),那么通过编写自己的复制 ctor 和赋值运算符来进行深度复制非常重要。
但是,如果你总是通过将它们作为 REFERENCE 传递来玩弄你的类,那么最好将默认的 copy ctor 和赋值运算符定义为私有的,这样即使你错误地传递了 valy 或复制,编译器也会警告你。
如果你不需要它,就不要遵循它。三法则背后的动机是,当你需要一个析构函数时,这通常是因为你需要做一些动态的释放。
如果还进行解除分配,则还需要复制构造函数和赋值运算符。想象一下,你有一个类,它有一个指向某物的指针:
struct Foo
{
Foo() { ptr_ = new int; }
~Foo() { delete ptr_; }
int* ptr_;
};
由于您没有定义 copy 构造函数和赋值运算符,因此每当您制作 的副本时,原始副本和副本都将使用指向相同 ;当原始文件或副本被销毁时,指针将被释放,从而使另一个指针具有不可用的数据。Foo
int
Foo(cont Foo& other) {
other.ptr_ = new int(*ptr_);
}
// Same for operator=
如果你没有在构造函数/析构函数中做任何动态分配,那么你实际上很可能不需要复制构造函数或赋值运算符(但不一定)。
评论