Does a default constructor always initialize all members?

匿名 (未验证) 提交于 2019-12-03 01:18:02

问题:

I could swear I don't remember having seen this before, and I'm having trouble believing my eyes:

Does an implicitly-defined default constructor for a non-aggregate class initialize its members or no?

In Visual C++, when I run this innocent-looking code...

#include  struct S { int a; std::string b; }; int main() { return S().a; } 

... to my astonishment, it returns a non-zero value! But if I remove field b, then it returns zero.

I've tried this on all versions of VC++ I can get my hands on, and it seems to do this on all of them.

But when I try it on Clang and GCC, the values are initialized to zero, whether I try it in C++98 mode or C++11 mode.

What's the correct behavior? Is it not guaranteed to be zero?

回答1:

Quoting C++11:

5.2.3 Explicit type conversion (functional notation) [expr.type.conv]

2 The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type,which is value-initialized (8.5; no initialization is done for the void() case). [...]

8.5 Initializers [dcl.init]

7 To value-initialize an object of type T means:

  • ...
  • if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is non-trivial, that constructor is called.
  • ...

So in C++11, S().a should be zero: the object is zero-initialized before the constructor gets called, and the constructor never changes the value of a to anything else.

Prior to C++11, value initialization had a different description. Quoting N1577 (roughly C++03):

To value-initialize an object of type T means:

  • ...
  • if T is a non-union class type without a user-declared constructor, then every non-static data member and base-class component of T is value-initialized;
  • ...
  • otherwise, the object is zero-initialized

Here, value initialization of S did not call any constructor, but caused value initialization of its a and b members. Value initialization of that a member, then, caused zero initialization of that specific member. In C++03, the result was also guaranteed to be zero.

Even earlier than that, going to the very first standard, C++98:

The expression T(), where T is a simple-type-specifier (7.1.5.2) for a non-array complete object type or the (possibly cv-qualified) void type, creates an rvalue of the specified type, whose value is determined by default-initialization (8.5; no initialization is done for the void() case).

To default-initialize an object of type T means:

  • if T is a non-POD class type (clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
  • ...
  • otherwise, the storage for the object is zero-initialized.

So based on that very first standard, VC++ is correct: when you add a std::string member, S becomes a non-POD type, and non-POD types don't get zero initialization, they just have their constructor called. The implicitly generated default constructor for S does not initialise the a member.

So all compilers can be said to be correct, just following different versions of the standard.

As reported by @Columbo in the comments, later versions of VC++ do cause the a member to be initialized, in accordance with more recent versions of the C++ standard.



回答2:

(All quotes in the first section are from N3337, C++11 FD with editorial changes)

I cannot reproduce the behavior with the VC++ on rextester. Presumably the bug (see below) is already fixed in the version they are using, but not in yours - @Drop reports that the latest release, VS 2013 Update 4, fails the assertion - while the VS 2015 preview passes them.

Just to avoid misunderstandings: S is indeed an aggregate. [dcl.init.aggr]/1:

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

That is irrelevant though.
The semantics of value initialization are important. [dcl.init]/11:

An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.

[dcl.init]/8:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;
  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
  • [..]

Clearly this holds regardless of whether b is in S or not. So at least in C++11 in both cases a should be zero. Clang and GCC show the correct behavior.


And now let's have a look at the C++03 FD:

To value-initialize an object of type T means:

  • if T is a class type (clause 9) with a user-declared constructor (12.1) [..]
  • if T is a non-union class type without a user-declared constructor, then every non-static data member and base-class component of T is value-initialized;
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized

That is, even in C++03 (where the above quote in [dcl.init]/11 also exists in /7), a should be 0 in both cases.
Again, both GCC and Clang are correct with -std=c++03.

As shown in hvd's answer, your version is compliant for C++98, and C++98 only.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!