C++11删除/默认构造函数

3

我有点困惑于C++11和C++17中构造函数的调用方式和原因。

#include <iostream>
using namespace std;

//---

template<typename T>
struct StructTest
{
public:
  const T Var = -1;

  //---

  // constexpr StructTest() = delete;  // 1
  // constexpr StructTest() = default; // 2

  // constexpr StructTest(const StructTest &Source) = delete;                  // 3
  // constexpr StructTest(const StructTest &Source) = default;                 // 4
  // constexpr StructTest(const StructTest &Source) {cout << "Copy" << endl;}; // 5
};

//---

StructTest<int> A{};
StructTest<int> A1{1};
StructTest<int> A2(A);

//---

int main(void)
{
  return(0);
};

当我取消注释一些行时(使用clang编译器和c++17标准),我对发生的事情感到困惑:

  • 1、编译成功。为AA1进行列表初始化,并为A2使用默认复制构造函数。
  • 2、编译成功。为A执行默认构造函数,为A1进行列表初始化(?),并为A2使用默认复制构造函数。
  • 1 + 3或2 + 3、编译失败,因为A2的复制构造函数被删除了。
  • 1 + 4、编译成功。为A执行默认构造函数,为A1进行列表初始化(?),并为A2使用默认复制构造函数。
  • 2 + 4、编译成功。为A执行默认构造函数,为A1进行列表初始化(?),并为A2使用默认复制构造函数。
  • 1 + 5、编译失败。提示A缺少(已删除)默认构造函数,并且没有匹配的构造函数可以用于A1
  • 2 + 5、编译失败。没有匹配的构造函数可以用于A1

我认为我理解了大部分内容,但是我不明白为什么1 + 5和2 + 5编译失败。有人能解释一下编译器选择要使用的构造函数的逻辑以及为什么会编译失败吗?

如果我错误地认为在其他情况下被调用的构造函数,请指出实际被调用的构造函数以及原因。


我看不出来1是如何编译的。StructTest<int> A{};需要默认构造函数。 - user4581301
然而它确实可以! - Matthieu Brucher
1
@user4581301 是的,它可以编译。我现在正在撰写答案解释原因。 - SergeyA
嗨@user4581301,我认为它正在使用A的直接列表初始化,因此不需要默认构造函数,但我不是完全确定(因此问题的最后一行!)! - Andy
什么都没有的聚合。甚至没有考虑到这一点。狡猾。 - user4581301
显示剩余2条评论
3个回答

9

1、编译。A和A1的列表初始化,以及A2的默认复制构造函数。

在这种情况下,你所谓的“列表初始化”实际上是 聚合初始化,因为StructTest是一个聚合体。这是允许的,因为显式默认或删除的构造函数仍然使类成为聚合体。

2、编译。A的默认构造函数和A1的列表初始化?,以及A2的默认复制构造函数。

A1像1中一样进行了聚合初始化。其他的都是正确的。

1 + 3 或 2 + 3,由于A2的已删除复制构造函数而无法编译。

这是预期的行为,因为复制构造函数被标记为已删除。

1 + 4,编译。A的默认构造函数和A1的列表初始化?,以及A2的默认复制构造函数。

同样,AA1进行了聚合初始化。

2 + 4,编译通过。A 的默认构造函数和 A2 的默认拷贝构造函数将被使用,A1 将使用列表初始化但仍会使用 Var 的默认成员初始化器进行初始化[dcl.init.aggr]/5.1

AA1 将被聚合初始化,但在初始化 A 时将使用 Var 的默认成员初始化器。

1 + 5,编译失败。显示 A 缺失(已删除)默认构造函数,A1 没有匹配的构造函数?

5 是用户提供的非默认或已删除构造函数。这意味着 StructTest 不再是一个聚合体,您无法再进行聚合初始化。

2 + 5,编译失败。A1 没有匹配的构造函数?

原因与 1 + 5 相同。


嗨@NathanOliver,非常感谢!我错过了关于非默认/已删除构造函数和聚合类的部分。不过我还有一个问题想问您,1+4和2+5这两种情况,它们调用完全相同的构造函数吗?我尝试再次阅读C++参考文献,并开始让自己相信1+4对A和A1执行了聚合初始化(因为默认构造函数被第1行删除),但是2+4调用了A的默认构造函数,并对A1执行了聚合初始化? - Andy
请注意,OP所谓的“List init”实际上是列表初始化。确切地说,它也是聚合初始化。聚合初始化是列表初始化的一种形式。 - eerorika
@Andy 在2 + 4中,它仍然是聚合初始化,只是它将使用默认成员初始化程序:https://timsong-cpp.github.io/cppwp/dcl.init.aggr#5.1 - NathanOliver

6

(这是其他答案的补充信息)

此代码在C++11、C++14/17和C++20中的行为不同!由于聚合体定义的变化。

在C++11中,该类不是聚合体,因为它有一个花括号或等号初始化器= -1),因此情况1将无法编译。

在C++14和17中,该类是一个聚合体,其他答案涵盖了这种情况。

在C++20中,该类将再次不是聚合体,因为有一个新规则:任何用户声明的构造函数都会使类失去聚合体资格;因此,情况1将再次停止编译,在情况2中,StructTest<int> A1{1};由于构造函数参数过多而无法编译,等等。


3
你所提到的“列表初始化”实际上被称为聚合初始化。你的类在除了取消注释第五行的情况下都是一个聚合类,取消注释后它将不再是一个聚合类。聚合类是指所有构造函数都默认(显式或隐式)或已删除的类。由于你只有一个非默认、非已删除的构造函数,因此除非你取消注释该行,否则你的类仍然是一个聚合类。
基于此,你的大多数示例都涉及聚合初始化,但如果你明确禁止复制(通过删除复制构造函数)或添加非默认复制构造函数并使类成为非聚合类,则不属于聚合初始化。
关于聚合和聚合初始化的更多信息:https://en.cppreference.com/w/cpp/language/aggregate_initialization

同样相关的是,std::is_aggregate - Eljay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接