常量成员堆栈与堆

Const member stack vs heap

本文关键字:堆栈 成员 常量      更新时间:2023-10-16

如果我尝试编译此代码

struct A {
    const int j;
};
A a;

我会收到一个预期的错误:

错误:"结构 A"中未初始化的 const 成员

但是,如果我尝试编译这个:

struct A {
    const int j;
};
A * a = new A();

我会得到一个成功的构建。

问题是:为什么new分配允许在没有显式构造函数和堆栈分配的情况下创建具有 const 成员的变量 - 不是吗?

这不是因为堆分配,而是因为您在分配时使用的括号。如果您这样做,例如

A* a = new A;

它也会失败。

添加括号时它起作用的原因是,您的结构是值初始化的,对于像 POD 类型A值初始化值初始化每个成员,int的默认值初始化为零。

这意味着如果您只添加值初始化括号,它可能也可以在堆栈上创建变量:

A a = A(); // watch out for the http://en.wikipedia.org/wiki/Most_vexing_parse

虽然这会带来其他潜在的问题,但如果可以的话,最好使用统一初始化(安C++11(:

A a{};

截至 C++14 的格式不正确。 §12.1 [class.ctor] 说

4 类 X 的默认构造函数定义为已删除 如果:

  • [...]
  • 任何没有大括号或等号初始值设定项的 const 限定类型(或其数组(的非变体非静态数据成员都没有 用户提供的默认构造函数,
  • [...]

如果默认构造函数不是用户提供的,并且

如果:
  • 它的类没有虚函数(10.3(和虚基类(10.1(,并且
  • 其类中没有非静态数据成员具有大括号或等于初始值设定项,并且
  • 其类的所有直接基类都有简单的默认构造函数,并且
  • 对于其类中属于类类型(或其数组(的所有非静态数据成员,每个此类都有一个简单的默认值 构造 函数。

§8.5 [dcl.init] 反过来说

7 默认初始化 T 类型的对象意味着:

  • 如果 T 是(可能符合 cv 条件的(类类型(条款 9(,则调用 T 的默认构造函数 (12.1((初始化为 如果 T 没有默认构造函数或重载解析,则格式不正确 (13.3( 导致歧义或功能被删除或 无法从初始化上下文中访问(;
  • [...]

8 对 T 类型的对象进行值初始化意味着:

  • 如果 T 是一个(可能符合 cv 条件的(类类型(条款 9(,没有默认构造函数 (12.1( 或默认构造函数 用户提供或删除,则对象默认初始化;
  • [...]

空括号对会导致值初始化。A的默认构造函数定义为根据 [class.ctor]/p4 删除。因此,通过 [dcl.init]/p8,值初始化意味着默认初始化,而到 p7 时,初始化格式不正确,因为构造函数被删除。

C++11版本实际上允许在这种情况下进行值初始化;它说对于值初始化,除其他外,

如果 T 是(可能符合 cv 条件的(非联合类类型,则没有 用户提供的构造函数,则对象初始化为零,如果 T 的隐式声明的默认构造函数是不平凡的,即 构造函数被调用。

由于根据上面的定义,默认构造函数是微不足道的(即使它被删除了(,所以实际上并没有调用它。这被认为是标准的缺陷,CWG第1301期更改了相关措辞。

编译器供应商通常会实现缺陷解决方案,因此这可以被视为 GCC 4.8 中的错误,该错误已在 4.9 中修复。