不同的隐式生成函数之间是否存在可观察到的语义差异?

Gre*_*ory 8 c++ language-lawyer

我一直在阅读 C++ 标准,试图了解平凡、简单和隐式定义的构造函数/赋值运算符/析构函数之间是否存在任何可观察到的差异。从我目前的理解来看,似乎没有区别,但这似乎很奇怪,为什么在无关紧要的情况下花这么多时间定义它们?

作为一个特定的具体示例,请考虑复制构造函数。

  • 如果所有字段和基类都是平凡的,那么平凡复制构造函数会逐个字段地复制所有字段和基类。
  • 否则,隐式生成的复制构造函数:“按初始化顺序执行基本成员和非静态成员的完整成员副本”。

如果我理解正确,如果一个类具有所有琐碎的基础和字段,但具有默认的复制构造函数,那么默认的复制构造函数将与普通构造函数做完全相同的事情。甚至初始化顺序在这里似乎都不相关,因为这些字段都是不相交的(因为平凡意味着没有virtual基类)。

有没有一个例子,一个简单的复制构造函数会做一些与显式默认的复制构造函数不同的事情?

通常,相同的逻辑似乎也适用于其他构造函数和析构函数。由于数据竞争的可能性,分配的参数有点复杂,但如果类实际上是微不足道的,那么所有这些似乎都是标准未定义的行为。

And*_*dyG 6

不完全是关于实际特殊成员函数本身* 的行为,但请考虑以下事项:

struct Normal
{
    int a;
};

static_assert(std::is_trivially_move_constructible_v<Normal>);
static_assert(std::is_trivially_copy_constructible_v<Normal>);
static_assert(std::is_copy_constructible_v<Normal>);
Run Code Online (Sandbox Code Playgroud)

这一切似乎都很好。

现在考虑以下几点:

struct Strange
{
    Strange() = default;
    Strange(Strange&&) = default; 
};

static_assert(std::is_trivially_move_constructible_v<Strange>);
static_assert(!std::is_trivially_copy_constructible_v<Strange>);
static_assert(!std::is_copy_constructible_v<Strange>);
Run Code Online (Sandbox Code Playgroud)

唔。仅显式默认移动构造函数的行为不允许对象可复制构造!

为什么是这样?

因为,即使编译器仍在为定义移动构造函数Strange,它仍然是用户声明的移动构造函数,它禁用了复制特殊成员函数的生成。

当您拥有用户声明的版本时,标准非常挑剔会生成哪些特殊成员函数,因此最好坚持五或零规则

现场演示


额外学分

通过显式默认 的默认构造函数Strange,它不再是聚合类型(而Normal实际上是)。这开启了一个完全不同的关于初始化的蠕虫。


*因为据我所知,显式默认的特殊成员函数的行为与该函数的普通版本相同(或者更确切地说,是相反的)。但是,我必须注意标准措辞的一个特点;在讨论隐式声明的复制构造函数时,标准忽略了“隐式声明为默认值”,就像它对默认和移动构造函数所做的那样。我相信这是一个小错误。