首页 > 解决方案 > 不同的隐式生成函数之间是否存在可观察到的语义差异?

问题描述

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

作为一个具体的例子,考虑复制构造函数。

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

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

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

标签: c++language-lawyer

解决方案


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

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>);

这一切似乎都很好。

现在考虑以下几点:

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>);

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

为什么是这样?

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

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

现场演示


额外学分

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


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


推荐阅读