【问题标题】:Why do we need to use virtual ~A() = default; instead of virtual ~A() {} in C++11?为什么我们需要使用 virtual ~A() = default;而不是 C++11 中的虚拟 ~A() {}?
【发布时间】:2013-06-17 18:48:35
【问题描述】:

在 Stack Overflow 帖子 Checking the object type in C++11 中,我有评论:

在 C++11 中,您实际上会想要执行 virtual ~A() = default; 否则,您将失去隐式移动构造函数。

virtual ~A() = default; 是干什么用的? virtual ~A() {} 为何会丢失隐式移动构造函数?

【问题讨论】:

  • +1,因为您设法引起了 10k+ 用户的错误回答。
  • 请注意,您始终可以使用相同的机制获得移动构造函数:A(A&&) = default;

标签: c++ c++11 destructor virtual-functions


【解决方案1】:

评论不正确。

两者:

virtual ~A() = default;

virtual ~A() {}

被用户声明。如果析构函数是用户声明的,则隐式移动成员将被禁止。

[dcl.fct.def.default]/p4 讨论 user-declareduser-provided 特殊成员:

一个特殊的成员函数是用户提供的,如果它是用户声明的并且 在第一次声明时未明确默认或删除。

【讨论】:

  • @HowardHinnant:那么在这两种情况下编译器都不会提供移动构造函数?
  • @Pixelchemist:我认为你是对的。第 8.4.2 节 Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. 这是来自 n3337 即 C++11 标准 + 1
  • 我认为默认函数的全部意义在于不抑制其他成员的生成,影响琐碎性等。
  • @DeadMG:不,默认的目的是让成员在因任何原因被压制时没有大量样板。
  • @Xeo 还有其他原因需要非用户提供的功能。例如。 ~A() = default; 可能是一个微不足道的析构函数,但 ~A() {} 不可能。
【解决方案2】:

在这个帖子https://stackoverflow.com/a/17204598/260127,我有评论:

在 C++11 中,您实际上需要执行 virtual ~A() = default; 否则,您将失去隐式移动构造函数。

评论不正确。

即使defaulted,该析构函数也是“user-declared”(但请注意,它也不是“user-provided”)。

#include <iostream>

struct Helper
{
    Helper() {}
    Helper(const Helper& src) { std::cout << "copy\n"; }
    Helper(Helper&& src)      { std::cout << "move\n"; }
};

struct A
{
    virtual ~A() {}
    Helper h;
};

struct B
{
    virtual ~B() = default;
    Helper h;
};

struct C
{
    Helper h;
};


int main()
{
    {
        A x;
        A y(std::move(x));   // outputs "copy", because no move possible
    }

    {
        B x;
        B y(std::move(x));   // outputs "copy", because still no move possible
    }

    {
        C x;
        C y(std::move(x));   // outputs "move", because no user-declared dtor
    } 
}

Live demo:

+ g++-4.8 -std=c++11 -O2 -Wall -pthread main.cpp
+ ./a.out
复制
复制
移动

所以你没有“丢失”任何东西——一开始就没有移动功能!

这是在两种情况下禁止隐式移动构造函数的标准段落:

[C++11: 12.8/9]: 如果类X 的定义没有明确声明移动构造函数,当且仅当

  • X 没有用户声明的复制构造函数,
  • X 没有用户声明的复制赋值运算符,
  • X 没有用户声明的移动赋值运算符,
  • X 没有用户声明的析构函数,并且
  • 移动构造函数不会被隐式定义为已删除。

引导说明

如果标准的未来版本实际上列出了诸如“用户声明”之类的术语的确切含义,那也没有什么坏处。至少有这个:

[C++11: 8.4.2/4]: [..] 一个特殊的成员函数是用户提供的,如果它是用户声明的并且在其第一次声明时没有显式默认或删除。 [..]

人们可以通过暗示假设这里的区别。

【讨论】:

  • 虽然我想知道为什么标准不允许实现在用户没有提供时提供移动构造函数,即未声明它并执行= default; 似乎一样。
  • @legends2k:我同意;理想情况下,12.8/9 可以改为“用户提供”。也可能是12.8/20
  • 这些隐含的特殊成员函数规则在 C++11 中变得更加复杂。 "explicitly defaulted or deleted on its first declaration" 是什么意思?第一个声明之后可以有后续声明吗?
  • @ThomasMcLeod:定义也是一个声明,所以,是的……除非你不能定义一个默认或删除的特殊成员函数。这里会有两个声明:struct A { ~A() = default; }; A::~A() {},除了定义是非法的。我认为这可能只是措辞尴尬或过于精确。
【解决方案3】:

那条评论是错误的。

如果您希望编译器提供一个,而不是提供您自己的移动构造函数,其中一个要求是它期望析构函数也由它提供,即一个微不足道的析构函数。然而,当前的标准对何时可以提供隐式实现非常严格——接受用户如何给出析构函数。用户声明的任何事情都被认为是用户将此事掌握在自己手中,因此不仅如此

~A() { … }

还有这个

~A() = default;

使编译器不提供隐式析构函数。首先是定义,因此也是声明;第二只是一个声明。在这两种情况下,析构函数都是用户声明的,因此禁止编译器提供隐式移动构造函数。

我猜这个要求背后的基本原理是,在 move 期间,一个对象的资源被移动到另一个对象,使原始对象处于动态存储中没有资源的状态;但是如果你的类没有任何这样的资源,那么它可以被简单地移动、销毁等。当你声明一个非平凡的析构函数时,它会提示编译器你在类中管理的资源不是微不足道的,而且您通常也必须提供重要的 move,因此编译器不提供。

【讨论】:

  • 除非它不是这种行为背后的原因,因为那是不是的行为。
  • 现已修复以解决 OP 问题。
猜你喜欢
  • 1970-01-01
  • 2021-04-28
  • 1970-01-01
  • 2013-10-21
  • 1970-01-01
  • 2011-03-01
  • 2011-07-07
  • 1970-01-01
  • 2019-09-29
相关资源
最近更新 更多