为什么常量成员结构体需要构造函数?

35

我有一段类似于这样的代码:

class AClass {
public:
  struct AStruct { };

  AClass(){}

private:
  const AStruct m_struct;
};

int main() {
  AClass a;
}

它抛出了这个编译错误(使用 Clang LLVM 版本 5.1):
error: constructor for 'AClass' must explicitly initialize 
       the const member 'm_struct'

如果我为struct AStruct指定一个C++11默认构造函数,我会得到相同的错误:
  struct AStruct {
    AStruct() = default;
  };

然而,可以通过编写一个空构造函数来解决这个问题:

  struct AStruct {
    AStruct(){}  // fixed
  };

为什么我需要指定一个空构造函数?结构体不是会自动创建具有公共访问权限的构造函数吗?

C++11的默认构造函数为什么不能解决这个问题?


7
你对 AStructAClass 的使用非常令人困惑,特别是因为它们都是类(class)。你能不能想出更好的名字? - Lightness Races in Orbit
2
这里有一个sscce版本 - Yakk - Adam Nevraumont
4
不必提供默认构造函数,对const成员值初始化即可 - http://coliru.stacked-crooked.com/a/aa3bcb77ce7b04eb - Praetorian
2个回答

27

根据§8.5 [dcl.init]/7规定,如果程序要求对const限定类型T的对象进行默认初始化,则T必须是一个类类型,并且该类必须具有用户提供的默认构造函数。

AClass的默认构造函数会对const成员进行默认初始化(详见下文),因此该成员必须有一个用户提供的默认构造函数。如在§8.4.2 [dcl.fct.def.default]/4中所示,使用“= default”并不会导致用户提供的默认构造函数。

如果一个函数在第一次声明时是用户声明的,且没有明确地被默认定义或删除,则该函数是用户提供的。


根据§12.6.2 [class.base.init]/8规定,该成员将进行默认初始化:

在非委托构造函数中,如果某个非静态数据成员或基类没有被mem-initializer-id指定(包括构造函数没有ctor-initializer的情况),且该实体不是抽象类的虚基类(10.4),则:

— 如果该实体是具有大括号或等号初始化器的非静态数据成员,则按照8.5中的规定进行初始化;
— 否则,如果该实体是匿名联合或变量成员(9.5),则不执行初始化;
否则,该实体将进行默认初始化(8.5)。


@chris 我使用的是G++-4.8。没有错误。我甚至可以调用struct A的const方法而不出错。那么,这个问题是clang特有的吗? - Peng Zhang
3
更糟糕的是,这段代码实际上正在执行(而且是有用的),但我们却收到编译器错误。@PengZhang,G++4.8在这里违反了标准,而不是clang。 - Yakk - Adam Nevraumont
3
@Casey 错误的问题编号 - T.C.
@chris,为了更加强调这个怪异的现象,如果=default在类声明之外定义,那么它被视为用户定义的构造函数,并且编译(8.4.2/5)[在示例中,a失败,b编译](http://coliru.stacked-crooked.com/a/5d06bb96b49b1f5c)。 - Niall
@Niall,我已经处理了那个问题 :) 如果一个函数在第一次声明时是用户声明的且没有显式地默认或删除,则该函数是用户提供的。 - chris

19

从@chris的答案中借鉴,我们有以下段落:§8.5 [dcl.init]/7:

如果程序调用一个const限定类型T的对象的默认初始化,那么T必须是一个类类型,并且具有用户提供的默认构造函数。

然后我们可以构造一个非常荒谬的情况来说明这个限制:

struct Foo {};
int main() {
  const Foo f;
}

这段代码在clang编译器中无法通过编译,因为它不符合标准规范。但如果将其作为另一个类/结构体的成员变量,则可以解决问题。

我们甚至可以这样做:

struct Foo {int x = 3;};
int main() {
  const Foo f;
}

这里明显初始化了所有数据。最后一个例子让我确认这是标准中的缺陷。

可能是因为POD类型未初始化并且是“const”,但措辞阻碍了与其无关的代码。在现代C++中,默认构造函数通常已足够好,强制使用Foo(){}是不合适的做法。


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