我的C ++ 11代码中有一个警告,我想正确修复,但我真的不知道如何解决。我已经创建了自己的异常类,该异常类是从std::runtime_error
派生的:
class MyError : public std::runtime_error
{
public:
MyError(const std::string& str, const std::string& message)
: std::runtime_error(message),
str_(str)
{ }
virtual ~MyError()
{ }
std::string getStr() const
{
return str_;
}
private:
std::string str_;
};
当我使用/Wall
用clang-cl编译该代码时,收到以下警告:
warning: definition of implicit copy constructor for 'MyError' is deprecated
because it has a user-declared destructor [-Wdeprecated]
因此,因为我在MyError
中定义了一个析构函数,所以不会为MyError
生成任何拷贝构造函数。我不完全了解这是否会引起任何问题...
现在我可以通过简单地删除虚拟析构函数来摆脱该警告,但我一直认为,如果基类(在这种情况下为std::runtime_error
)具有虚拟析构函数,则派生类应该具有虚拟析构函数。
因此,我想最好不要删除虚拟析构函数,而要定义副本构造函数。但是,如果我需要定义副本构造函数,也许我也应该定义副本赋值运算符,移动构造函数和移动赋值运算符。但这对于我简单的异常类来说似乎太过分了!?
有什么想法可以最好地解决此问题吗?
答案 0 :(得分:5)
现在我可以通过简单地删除虚拟析构函数来摆脱该警告,但是我一直认为,如果基类(在本例中为std :: runtime_error)具有虚拟析构函数,则派生类应该具有虚拟析构函数。
您认为错了。如果您在基中定义一个,则派生类将始终具有虚拟析构函数,无论是否显式创建它。因此,删除析构函数将是最简单的解决方案。如您在std::runtime_exception
的{{3}}中所见,它也不提供自己的析构函数,它是由编译器生成的,因为基类std::exception
确实具有虚拟dtor。
但是如果您确实需要析构函数,则可以显式添加编译器生成的副本ctor:
MyError( const MyError & ) = default;
或禁止它使类不可复制:
MyError( const MyError & ) = delete;
与赋值运算符相同。
答案 1 :(得分:3)
您无需在派生类中显式声明析构函数:
§15.4析构函数[class.dtor] (重点是我的)
可以将析构函数声明为虚拟(13.3)或纯虚拟(13.4);如果 该类或任何派生类的任何对象都在 在程序中,应该定义析构函数。如果班级有基班 使用虚拟析构函数,其析构函数(无论是用户还是 隐式声明)是虚拟的。
实际上,在某些情况下甚至可能会损害性能,因为显式声明析构函数将防止隐式生成move构造函数和move赋值运算符。
除非您需要在析构函数中执行某些操作,否则最好的做法是忽略析构函数的显式声明。
如果您确实需要自定义析构函数,并且确定默认的复制ctor,复制分配运算符,move ctor和move赋值运算符将为您做正确的事情,则最好像下面这样明确地默认它们:>
MyError(const MyError&) = default;
MyError(MyError&&) = default;
MyError& operator=(const MyError&) = default;
MyError& operator=(MyError&&) = default;
关于您为什么看到此错误的一些原因,因为这曾经是C ++ 98中的完美有效代码:
从C ++ 11开始,复制构造函数的隐式生成被声明为已弃用。
§D.2复制函数的隐式声明[depr.impldec]
默认情况下,复制构造函数的隐式定义为 如果该类具有用户声明的副本分配运算符,则不推荐使用 或用户声明的析构函数。副本的隐式定义 如果该类具有一个默认值,则不推荐使用默认的赋值运算符 用户声明的副本构造函数或用户声明的析构函数(15.4, 15.8)。在此国际标准的将来版本中,这些隐式定义可能会被删除(11.4)。
本文所依据的原理是众所周知的三法则。
以下所有报价均来自cppreference.com:https://en.cppreference.com/w/cpp/language/rule_of_three
如果类需要用户定义的析构函数,则为用户定义的副本 构造函数或用户定义的副本分配运算符,它几乎 当然需要全部三个。
之所以存在此经验法则,是因为默认生成的dtor,copy ctor和赋值运算符用于处理不同类型的资源(最主要的是指向内存的指针,还有其他指针,例如文件描述符和网络套接字,仅以此命名)。夫妇)很少会做出正确的举动。如果程序员认为他需要特殊的处理来关闭类析构函数中的文件句柄,那么他肯定会定义如何复制或移动此类。
为完整起见,以下是经常相关的5规则和有些争议的零规则
由于存在用户定义的析构函数,复制构造函数, 或复制分配运算符可防止隐式定义移动 构造函数和move赋值运算符,其移动的任何类 语义是可取的,必须声明所有五个特殊成员 功能:
具有自定义析构函数,复制/移动构造函数或 复制/移动分配操作员应专门处理所有权 (遵循“单一责任原则”)。其他 类不应具有自定义析构函数,复制/移动构造函数或 复制/移动分配运算符。
答案 2 :(得分:1)
注意:对于许多不同的代码,也会发生同样的情况,但是我在这里写是为了防止有人得到相同的警告。
GCC版本6.4-9.0中存在一个错误,其中在从base_type继承的类型中使用base_type的operator =和base_type的ctor声明,实际上并未创建复制/移动ctor /操作符 (导致非常意外的编译器错误,即无法复制/移动对象)。
自GCC 9.0起,此错误已修复,但会创建此警告。警告是错误的,不应出现(使用显式声明构造函数/运算符)。