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

时间:2018-08-15 17:28:56

标签: 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_;
};

当我使用/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)具有虚拟析构函数,则派生类应该具有虚拟析构函数。

因此,我想最好不要删除虚拟析构函数,而要定义副本构造函数。但是,如果我需要定义副本构造函数,也许我也应该定义副本赋值运算符,移动构造函数和移动赋值运算符。但这对于我简单的异常类来说似乎太过分了!?

有什么想法可以最好地解决此问题吗?

3 个答案:

答案 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起,此错误已修复,但会创建此警告。警告是错误的,不应出现(使用显式声明构造函数/运算符)。