use*_*460 14 c++ abstract-class rule-of-three virtual-destructor c++17
在定义接口类时声明实例化方法的正确方法是什么?
出于显而易见的原因,抽象基类需要具有虚拟析构函数.但是,然后给出以下编译警告:"'InterfaceClass'定义了一个非默认的析构函数,但没有定义复制构造函数,复制赋值运算符,移动构造函数或移动赋值运算符",这是"五的规则" ".
我理解为什么一般应该遵守"五规则",但是它仍适用于抽象基类或接口吗?
那么我的意思是:
class InterfaceClass
{
// == INSTANTIATION ==
protected:
// -- Constructors --
InterfaceClass() = default;
InterfaceClass(const InterfaceClass&) = default;
InterfaceClass(InterfaceClass&&) = default;
public:
// -- Destructors --
virtual ~InterfaceClass() = 0;
// == OPERATORS ==
protected:
// -- Assignment --
InterfaceClass& operator=(const InterfaceClass&) = default;
InterfaceClass& operator=(InterfaceClass&&) = default;
// == METHODS ==
public:
// Some pure interface methods here...
};
// == INSTANTIATION ==
// -- Destructors --
InterfaceClass::~InterfaceClass()
{
}
Run Code Online (Sandbox Code Playgroud)
它是否正确?这些方法应该= delete改为吗?有没有办法声明析构函数是虚拟的纯粹,同时还有某种方式保持默认?
即使我将析构函数声明为:virtual ~InterfaceClass() = default;,如果我没有明确地默认其他四个,那么我将获得相同的编译器警告.
Tl; dr:满足接口类的"五规则"的正确方法是什么,因为用户必须定义虚拟析构函数.
感谢您的时间和帮助!
它是否正确?这些方法应该改为= delete吗?
您的代码似乎正确。当您尝试多态复制派生类时,很明显需要将特殊的复制/移动成员函数定义为默认值和受保护。考虑以下附加代码:
#include <iostream>
class ImplementationClass : public InterfaceClass
{
private:
int data;
public:
ImplementationClass()
{
data=0;
};
ImplementationClass(int p_data)
{
data=p_data;
};
void print()
{
std::cout<<data<<std::endl;
};
};
int main()
{
ImplementationClass A{1};
ImplementationClass B{2};
InterfaceClass *A_p = &A;
InterfaceClass *B_p = &B;
// polymorphic copy
*B_p=*A_p;
B.print();
// regular copy
B=A;
B.print();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
并考虑4个用于在InterfaceClass中定义特殊的复制/移动成员函数的选项。
通过在InterfaceClass中删除特殊的复制/移动成员函数,可以防止多态复制:
*B_p = *A_p; // would not compile, copy is deleted in InterfaceClass
Run Code Online (Sandbox Code Playgroud)
这很好,因为多态复制也将无法共同复制派生类中的数据成员。
另一方面,您还可以防止普通复制,因为如果没有基类的复制赋值运算符,编译器将无法隐式生成复制赋值运算符:
B = A; // would not compile either, copy assignment is deleted in ImplementationClass
Run Code Online (Sandbox Code Playgroud)
使用复制/移动特殊成员函数作为默认成员和公共成员(或未定义复制/移动成员函数)的情况,普通复制将起作用:
B = A; //will copile and work correctly
Run Code Online (Sandbox Code Playgroud)
但将启用多态复制并导致切片:
*B_p = *A_p; // will compile but not copy the extra data members in the derived class.
Run Code Online (Sandbox Code Playgroud)
如果未定义move&copy特殊成员函数,则关于copy的行为类似于2:编译器将隐式生成已弃用的copy特殊成员(导致多态切片)。但是,在这种情况下,编译器不会隐式生成移动特殊成员,因此将在可能移动的地方使用复制。
使用特殊的复制/移动成员功能作为默认值并受保护,如您的示例所示,您将防止多态复制,这可能导致切片:
*B_p = *A_p; // will not compile, copy is protected in InterfaceClass
Run Code Online (Sandbox Code Playgroud)
但是,编译将显式生成默认的副本分配运算符InterfaceClass,而ImplementationClass将能够隐式生成其副本分配运算符:
B = A; //will compile and work correctly
Run Code Online (Sandbox Code Playgroud)
因此,您的方法似乎是最好,最安全的选择
对于析构函数,如果你想让它既是纯虚拟的又是默认的,你可以在实现中默认它:
class InterfaceClass
{
// -- Destructors --
virtual ~InterfaceClass() = 0;
};
InterfaceClass::~InterfaceClass() = default;
Run Code Online (Sandbox Code Playgroud)
不过,如果析构函数是默认的或空的,则没有太大区别。
现在回答你剩下的问题。
通常,您应该默认设置复制构造函数和赋值运算符。这样,它们就不会阻止在派生类中创建默认赋值运算符和复制构造函数。默认实现是正确的,因为没有可复制的不变量。
因此,如果你想轻松实现Clone方法,删除复制构造函数会有害:
class InterfaceClass
{
virtual InterfaceClass* Clone() = 0;
virtual ~InterfaceClass() = 0;
};
class ImplementationClass : public InterfaceClass
{
public:
// This will not work if base copy constructor is deleted
ImplementationClass(const ImplementationClass&) = default;
// Writing copy constructor manually may be cumbersome and hard to maintain,
// if class has a lot of members
virtual ImplementationClass* Clone() override
{
return new ImplementationClass(*this); // Calls copy constructor
}
};
Run Code Online (Sandbox Code Playgroud)
另请注意,复制/移动构造函数的默认实现不会意外地违背意图使用 - 因为无法创建抽象基类的实例。因此,您将始终复制派生类,并且它们应该定义复制是否合法。
然而,对于某些类来说,完全复制是没有意义的,在这种情况下,禁止在基类中进行复制/分配可能是明智的。
Tl;dr:这取决于情况,但很可能您最好将它们保留为默认值。