首页 > 解决方案 > 冲突的 CLANG `virtual dtor` 和 `deprecated copy operator` 警告

问题描述

在我的 Qt 项目中,我有一个如下所示的课程。我收到以下警告(已理解),并添加了一个虚拟 dtor。

dbmappingcomponentaware.h:25: warning: CDbMappingComponentAware has virtual functions but non-virtual destructor

现在我明白了

warning: definition of implicit copy assignment operator for 'CDbMappingComponentAware' is deprecated because it has a user-declared destructor

该类是一个普通的类,我没有声明任何运算符。我将如何解决这种冲突?

至于当有用户定义的析构函数时如何禁用隐式定义的复制构造函数生成,看起来我必须接受“已弃用”警告,但我不确定。

代码片段:

//! Allows subcomponents to gain access to model component
class CDbMappingComponentAware {
  public:
    //! Destructor (added for 1st warning)
    // virtual ~CDbMappingComponentAware() {}

    //! Set the corresponding component
    virtual void setMappingComponent(CDbMappingComponent *component);
...
...

-- 编辑,根据评论 --

阅读https://en.cppreference.com/w/cpp/language/copy_assignment它说,

如果 T 具有用户声明的析构函数或用户声明的复制构造函数,则不推荐生成隐式定义的复制赋值运算符(C++11 起)。

但这是为什么呢?我的意思是,为什么 dtor 会影响复制操作员?

标签: c++qtclang

解决方案


至于当有用户定义的析构函数时如何禁用隐式定义的复制构造函数生成,看起来我必须接受“已弃用”警告,但我不确定。

您不必接受“已弃用”警告。您在这里有 3 个选项,实现运算符、显式默认运算符或删除运算符。

首先,这个警告是触发的,因为你实际上使用了copy assignment operator某处。甚至可能不是故意的(参见//unfortunate mistake下面的示例)。除了使用运算符的警告之外,您实际上应该得到一个注释,例如:

note: in implicit copy assignment operator for 'CDbMappingComponentAware' first required here

考虑以下示例(运行版本):

class foo {
public:
    virtual ~foo() {}

    // options
    // implement
    //foo& operator=(foo const&) {return *this;}
    // default
    //foo& operator=(foo const&) = default;
    // delete
    //foo& operator=(foo const&) = delete;
};

class bar : public foo {
public:
    ~bar() override {};
};

// stupid mistake... should be defined
// void fn(bar const&)
void fn(bar) {}


int main(int, char*[]) {
    foo* f1 = new bar;
    foo* f2 = new bar;

    // triggers warning
    *f1 = *f2;

    // unfortunate mistake
    bar b;
    // triggers warning twice since bar inherits from foo
    fn(b);

    return 0;
}

在这种情况下,你真的在​​使用copy assignment operator,你应该实现它,或者如果它真的是普通的 vanilla ,则默认它。请参阅下面为什么您最终不应该默认它。如果您根本不想复制实例,请删除operator/ctor。

顺便说一句,复制构造函数也是一样的,不仅仅是赋值运算符。在§15.8.1/6中描述了复制构造函数,在§15.8.2/2中描述了草案n4727中的复制赋值运算符。

然而:

但这是否意味着如果我定义了一个虚拟 dtor(除非我明确地编写了一个复制赋值运算符),我就不能复制一个对象。

不,只要是警告,您仍然可以使用生成的复制 ctor/assignment。已弃用意味着它将(可能)在未来成为一个错误(如果是 C++,可能在大约 40 年或更长时间内,如果有的话)。因此,如果您不希望您的代码将此警告作为错误吐出,当编译器不再支持它时,您应该立即修复它。


但这是为什么呢?我的意思是,为什么 dtor 会影响复制操作员?

这里的假设答案:

我能想到的一个目的是防止一些错误。考虑这个例子(取自为什么我的构造函数不能正常工作? ):

class Handle {
private:
    string name;
    X* p;
public:
    Handle(string n)
        :name(n), p(0) { /* acquire X called "name" and let p point to it */ }
    ~Handle() { delete p; /* release X called "name" */ }
};
void f(const string& hh)
{
    Handle h1(hh);
    Handle h2 = h1; // leads to disaster!
}

在这里,默认副本为我们提供了 h2.name==h1.name 和 h2.p==h1.p。这导致了灾难:当我们退出 f() 时,h1 和 h2 的析构函数被调用,并且 h1.p 和 h2.p 指向的对象被删除了两次。

为了防止双重删除,您必须明确并且不依赖于生成的/默认的复制构造函数/赋值。

你是说警告是为了强制执行三规则吗?

在这种特殊情况下:的,我会这么说。

由于我不处理资源,因此三规则在这里没有真正意义。

这将要求编译器将成员考虑在内。为这样一个不成熟的警告付出了相当大的努力。

个人想法:

恕我直言,我不相信这会成为 C++ 中的错误。依赖于自动生成的 ctor/assignment 有很多代码。将其作为错误会破坏(字面上)数以千计的项目。-> 不会发生...


推荐阅读