我们假设我们有一个非常基本的class A
:
class A {
public:
void SetName(const std::string& newName) {
m_name=newName;
}
void Print() const {
std::printf("A::Print(). Name: %s\n",m_name.c_str());
}
private:
std::string m_name;
};
我们希望使用class B
扩展此类,因此我们添加虚拟析构函数,将成员更改为virtual
并将private
更改为protected
以获取inh:
class A {
public:
virtual ~A() {}
void SetName(const std::string& newName) {
m_name=newName;
}
virtual void Print() const {
std::printf("A::Print(). Name: %s\n",m_name.c_str());
}
protected:
std::string m_name;
};
class B : public A {
public:
virtual void Print() const {
std::printf("B::Print(). Name: %s\n",m_name.c_str());
}
};
既然我们在class A
中添加了析构函数,我们是否需要创建一个复制构造函数和复制运算符?
class A {
public:
virtual ~A() {}
A() = default;
A(const A& copyFrom){
*this = copyFrom;
}
virtual A& operator=(const A& copyFrom){
m_name=copyFrom.m_name;
return *this;
};
void SetName(const std::string& newName) {
m_name=newName;
}
virtual void Print() const {
std::printf("A::Print(). Name: %s\n",m_name.c_str());
}
protected:
std::string m_name;
};
对我来说,这似乎是不必要的,因为默认的复制操作符和复制构造函数会做同样的事情。
答案 0 :(得分:16)
为了为语言的潜在未来发展做好准备,在添加虚拟析构函数时,您确实应该显式默认复制/移动构造函数和赋值运算符。这是因为当类具有用户声明的析构函数时,C ++ 11,12.8 / 7会隐式生成复制构造函数。
幸运的是,C ++ 11的显式默认使其定义变得容易:
class A {
public:
virtual ~A() {}
A() = default;
A(const A& copyFrom) = default;
A& operator=(const A& copyFrom) = default;
A(A &&) = default;
A& operator=(A &&) = default;
void SetName(const std::string& newName) {
m_name=newName;
}
virtual void Print() const {
std::printf("A::Print(). Name: %s\n",m_name.c_str());
}
protected:
std::string m_name;
};
答案 1 :(得分:11)
三规则适用于所有事物。
如果您的类打算用作多态基础,那么您很可能不希望使用其复制构造函数,因为它会切片。所以你必须做出决定。这就是三个规则的含义:你不能选择在不考虑复制特殊成员的情况下使用析构函数。
请注意,三条规则并未说明您应该实现复制构造函数和复制赋值运算符。你应该以某种方式处理它们,因为如果你有自己的析构函数(它切片!),默认生成的那个很可能不合适,但你处理它们的方式不一定要实现它们。 / p>
你可能应该禁止它,因为使用多态基础和价值语义倾向于像水和油一样混合。
我想你可能会让它受到保护,所以派生类可以为自己的副本调用它,尽管我仍然认为这是一个值得怀疑的选择。
此外,从C ++ 11开始,当用户声明析构函数时,不推荐使用复制特殊成员的生成。这意味着,如果您希望代码是向前兼容的,即使您需要默认的复制构造函数行为(一个可疑的选择),您也希望将其明确化。您可以使用= default
。
答案 2 :(得分:4)
如果析构函数没有做任何事情,那么(通常)不需要复制/移动操作来执行除默认操作之外的任何操作。当然不需要编写那些执行默认设置的版本,只是为了满足规则的过度简化。所有这一切都会增加代码的复杂性和错误的范围。
但是,如果您声明了一个虚拟析构函数,指示该类是一个多态基类,您可以考虑删除复制/移动操作以防止切片。
This article为该规则提供了有用的措辞,包括
如果一个类有一个非空析构函数,它几乎总是需要一个复制构造函数和赋值运算符。