常量成员栈 vs 堆

13

如果我尝试编译这段代码

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

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

error: uninitialized const member in ‘struct A’

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

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

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

问题是:为什么使用new分配允许创建具有const成员变量且没有显式构造函数的变量,而堆栈分配则不允许?


1
无法在GCC 4.9或Clang中编译。http://coliru.stacked-crooked.com/a/fadd1a456bfd5b82 - T.C.
@remyabel 这显然并不意味着这段代码的正确性。 - T.C.
2个回答

10

问题不在于堆分配,而在于您在分配时使用的括号。如果您这样做:

A* a = new A;

加括号后代码能正常运行的原因是,这样你的结构体就被值初始化了,对于POD类型(例如 A)来说,值初始化会初始化每个成员变量,而int 的默认值初始化为零。

这意味着如果你在堆栈上创建变量时只需添加值初始化括号,它也可能会正常工作:

it would also fail.

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

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

A a{};

1
是的,它会被视为函数声明。 - ravi
A a = A(); 可以工作,但 A a{}; 真的可以工作吗? - juanchopanza
好的。谢谢。我理解得对:如果我在变量后面写(),那么对于 POD 类型,它只会进行成员初始化而不是调用构造函数? - borisbn

6
根据C++14标准,这是不合法的。§12.1 [class.ctor]指出:
4 如果X类的默认构造函数是默认的,则定义为删除的,如果:
  • [...]
  • 任何非变体的const限定类型(或其数组)的非静态数据成员没有用户提供的默认构造函数且没有花括号或等号初始化器
  • [...]
如果默认构造函数不是用户提供的,并且满足以下条件,则它是平凡的:
  • 它的类没有虚函数(10.3)和虚基类(10.1),
  • 它的类中没有非静态数据成员有花括号或等号初始化器
  • 它的类的所有直接基类都有平凡的默认构造函数,
  • 对于其类类型(或其数组)的所有非静态数据成员,每个这样的类都有一个平凡的默认构造函数。
而§8.5 [dcl.init]则表示:
7 对于类型T的默认初始化对象意味着:
  • 如果T是(可能是cv-qualified的)类类型(第9条),则会调用T的默认构造函数(12.1)(如果T没有默认构造函数或重载决议(13.3)导致二义性或在初始化上下文中不可访问或删除,则初始化是不合法的);
  • [...]
8 对于类型T的值初始化对象意味着:
  • 如果T是(可能是cv-qualified的)类类型(第9条),并且没有默认构造函数(12.1)或者有一个用户提供或已删除的默认构造函数,则进行默认初始化;
  • [...]
空括号对导致值初始化。根据[class.ctor]/p4,A的默认构造函数被定义为删除。因此,根据[dcl.init]/p8,值初始化意味着默认初始化,并且根据p7,由于构造函数被删除,初始化是不合法的。
实际上,C++11版本允许在此上下文中进行值初始化;对于值初始化,它表示:
  • 如果T是(可能是cv-qualified的)非联合类类型且没有用户提供的构造函数,则对象将被零初始化,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数。

根据上述定义,即使默认构造函数被删除,它仍然是平凡的,因此实际上不会被调用。这被认为是标准中的一个缺陷,并且相关语言已经由CWG issue 1301进行了修改。

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


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