【问题标题】:=default in declaration vs definition=声明与定义中的默认值
【发布时间】:2018-12-22 18:08:00
【问题描述】:

我知道,而不是写作:

class A {
public:
    A(A&&) noexcept = default;
};

最好写

class A {
public:
    A(A&&) noexcept;
};

inline A::A(A&&) noexcept = default;

我听说的原因是:

  1. 避免构造函数变成deleted。如果无法定义函数,编译器会报错。

  2. 移动构造函数被声明为noexcept,即使某些成员字段的移动构造函数没有使用noexcept注解。

有人能详细解释一下这些差异背后的理论吗?

【问题讨论】:

  • 我可以验证第一点是正确的:为了尽早发现编译错误,最好在类定义中使用default(参见godbolt.org/g/36iQb8)。
  • @bipll 是真的。相信我。
  • 我在玩过上面链接中的代码后观察到,在类定义中,无论如何都会生成默认构造函数,在类中的时候声明默认构造函数将“按需”生成,如果在代码中使用。但我对理论一无所知,无论是否指定了这种行为。
  • @bipll 我能找到的唯一参考资料可能在这里:akrzemi1.wordpress.com/2015/09/11/…
  • 此行为在 [dcl.fct.def.default] 中描述。如果使用与隐式声明的特殊成员将具有的显式异常规范不匹配的显式异常规范声明特殊成员函数,则 (a) 如果在第一个声明中(即在类定义中)使用=default,则它是与=delete 的含义相同,或者(b) 如果=default 用于稍后的声明(在类定义之外),则程序格式错误。看起来相关措辞将从 C++17 更改为 C++20,但两个版本中的异常规范都是正确的。

标签: c++ default-constructor noexcept


【解决方案1】:

只有声明是用来描述类/方法的,所以在做的时候

class A {
public:
    A(A&&) noexcept;
};

您甚至可以根据需要实现 A::A(A&&)(定义可以在不同的 TU 中)

当你实现它时:

A::A(A&&) noexcept = default;

编译器必须生成方法(它无法判断它是否被隐式删除,因为声明精确方法存在),如果不能,则提供诊断。

但是当你在类中声明它时:

class A {
public:
    A(A&&) noexcept = default;
};

它是声明的“一部分”。所以它可能会被隐式删除(因为成员或基类)。

同样适用于noexcept

将定义放在专用 TU 中的另一个优点是,所需依赖项的定义只能在该 TU 中,而不是在生成方法的每个地方。 (例如对 pimpl 习惯用法很有用)。

拆分定义和声明的一个缺点是该方法现在是“用户提供的”,这可能会影响特性,如 trivially_constructible/copyable/...

【讨论】:

  • 高质量的答案,教会了我一些东西,点赞。但总的来说,在我看来,你会在类声明中看到它。
  • “编译器必须生成方法”。这是我很好奇的部分,因为我不知道哪个规范有助于它,或者它只是关于实现?
【解决方案2】:

[dcl.fct.def.default]p3 涵盖了该行为,其中写道:

如果一个显式默认的函数用 noexcept 说明符声明,不会产生相同的 异常规范作为隐式声明(18.4),然后

(3.1) — 如果函数在其第一个声明中明确默认,则将其定义为已删除;

(3.2) — 否则,程序格式错误。

注意in C++20 的措辞变化,但这种情况下的intent is the same。我发现 C++17 的措辞更容易理解。

例如给出:

struct S {
      S( S&& ) noexcept(false) = default;
};

由于[except.spec]p7,移动构造函数被定义为删除:

类 X 的隐式声明构造函数,或默认没有 noexcept 说明符的构造函数 在它的第一个声明中,有一个潜在的抛出异常规范当且仅当以下任何一个 构造是潜在的抛出

(7.1) — 在类 X 的构造函数的隐式定义中通过重载决议选择的构造函数 初始化一个可能构造的子对象,或

(7.2) — 这种初始化的子表达式,例如默认参数表达式,或者,

(7.3) — 对于默认构造函数,默认成员初始化器。

没有一种情况成立。

如果我们回到 [dcl.fct.def.default]p3,它会说否则程序是 格式错误的。格式错误的程序需要诊断,因此如果我们将第一个示例修改如下 (see it live):

struct S {
   S( S&& ) noexcept(false) ;
private:
  int i;

};

S::S( S&&) noexcept(false) = default ;

它将产生一个诊断,例如:

error: function 'S::S(S&&)' defaulted on its redeclaration with an exception-specification that differs from the implicit exception-specification 'noexcept'
 S::S( S&&) noexcept(false) = default ;
 ^

注意clang bug related to this case,他们似乎没有关注Defect Report 1778

您可能需要注意Declaring a function as defaulted after its first declaration,其中涵盖了一些优化/界面问题。

【讨论】:

  • 感谢您的回答!我认为它解释了第一点(删除的构造函数),那么第二点呢?我也不太明白“格式错误的程序需要诊断”是什么意思。
  • @laike9m 我对第二点并不完全确定,我觉得可能是“在第一次声明后声明一个函数为默认函数”,我链接到它,但不确定 2 的去向。至于需要诊断的格式不正确,我介绍了其中的一些 in my answer here
猜你喜欢
  • 2018-08-19
  • 2016-11-17
  • 2021-09-17
  • 1970-01-01
  • 2013-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多