警告:不建议使用隐式复制构造函数的定义

Lin*_*umz 11 c++ visual-c++ clang++

我在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_;
};
Run Code Online (Sandbox Code Playgroud)

当我使用clang-cl编译该代码时,/Wall我得到以下警告:

warning: definition of implicit copy constructor for 'MyError' is deprecated 
         because it has a user-declared destructor [-Wdeprecated]
Run Code Online (Sandbox Code Playgroud)

因为我已经定义了一个析构函数,MyError所以不会生成复制构造函数MyError.我不完全明白这是否会引起任何问题......

现在我可以通过简单地删除虚拟析构函数来消除该警告,但我总是认为如果基类(在本例中std::runtime_error)具有虚拟析构函数,派生类应该具有虚拟析构函数.

因此,我想最好不要删除虚拟析构函数,而是定义复制构造函数.但是如果我需要定义复制构造函数,也许我还应该定义复制赋值运算符和移动构造函数以及移动赋值运算符.但这对我的简单异常类似乎有点过分了!?

任何想法如何最好地解决这个问题?

div*_*nas 10

您无需在派生类中显式声明析构函数:

§15.4析构函数[class.dtor] (强调我的)

可以将析构函数声明为虚拟(13.3)或纯虚拟(13.4)。如果在程序中创建了该类的任何对象或任何派生类,则应定义析构函数。如果一个类具有带有虚拟析构函数的基类,则其析构函数(无论是用户声明的还是隐式声明的)都是virtual

实际上,在某些情况下,它甚至可能会损害性能,因为显式声明一个析构函数将防止隐式生成move构造函数和move赋值运算符。

除非您需要在析构函数中执行某些操作,否则最好的做法是忽略析构函数的显式声明。

如果确实需要自定义析构函数,并且确定默认的复制ctor,复制赋值运算符,move ctor和move赋值运算符会为您做正确的事情,那么最好像下面这样显式地默认它们:

MyError(const MyError&) = default;
MyError(MyError&&) = default;
MyError& operator=(const MyError&) = default;
MyError& operator=(MyError&&) = default;
Run Code Online (Sandbox Code Playgroud)

关于您为什么会看到错误的一些原因,因为在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规则和有些争议的零规则

五法则

因为用户定义的析构函数,复制构造函数或复制分配运算符的存在阻止了对移动构造函数和移动赋值运算符的隐式定义,所以需要移动语义的任何类都必须声明所有五个特殊成员函数:

零法则

具有自定义析构函数,复制/移动构造函数或复制/移动赋值运算符的类应专门处理所有权(遵循“单一职责原则”)。其他类不应具有自定义析构函数,复制/移动构造函数或复制/移动分配运算符。

  • 请注意,如果您打算通过指向 base 的指针删除对象,则仍然需要虚拟析构函数。在您的特定情况下, std::exception 有一个声明为 virtual 的构造函数,因此您无需担心它。如果您正在创建一个用户类,您拥有基类,那么您肯定应该在基类中声明一个虚拟析构函数。正如有关零规则的链接文章所说,在这种情况下,您应该明确默认所有 5 个特殊成员函数。 (2认同)

Sla*_*ica 5

现在我可以通过简单地删除虚拟析构函数来消除该警告,但我总是认为如果基类(在本例中为std :: runtime_error)具有虚拟析构函数,派生类应该具有虚拟析构函数.

你错了.如果您在base中定义一个派生类,那么派生类将始终具有虚拟析构函数,无论您是否明确创建它.因此,删除析构函数将是最简单的解决方案.正如您在文档中看到的那样,std::runtime_exception它也没有提供它自己的析构函数,而且它是编译器生成的,因为基类std::exception确实有虚拟dtor.

但是如果你确实需要析构函数,你可以显式添加编译器生成的副本ctor:

MyError( const MyError & ) = default;
Run Code Online (Sandbox Code Playgroud)

或禁止使课堂不可复制:

MyError( const MyError & ) = delete;
Run Code Online (Sandbox Code Playgroud)

赋值运算符相同.


Xev*_*ous 5

注意:很多不同的代码也会发生同样的情况,但我在这里写它以防有人收到相同的警告。

在 GCC 6.4 - 9.0 版本中存在一个错误,其中在从 base_type 继承的类型使用 base_type 的 operator= 和 base_type ctor 的声明实际上并没有创建复制/移动 ctor/operators(以非常意外的编译器错误结束,对象不能复制/移动)。

从 GCC 9.0 开始,该错误已修复,但它会创建此警告。警告是错误的,不应出现( using 显式声明构造函数/运算符)。