简体   繁体   中英

C++11 strange brace initialization behavior

I don't understand how C++11 brace initialization rules work here. Having this code:

struct Position_pod {
    int x,y,z;
};

class Position {
public:
    Position(int x=0, int y=0, int z=0):x(x),y(y),z(z){}
    int x,y,z;
};

struct text_descriptor {
    int             id;
    Position_pod    pos;
    const int       &constNum;
};

struct text_descriptor td[3] = {
     {0, {465,223}, 123},
     {1, {465,262}, 123},
};

int main() 
{
    return 0;
}

Note, that the array is declared to have 3 elements, but only 2 initializers are provided.

However it compiles without error, which sounds strange, as the last array element's reference member will be uninitialized. Indeed, it has NULL value:

(gdb) p td[2].constNum 
$2 = (const int &) @0x0: <error reading variable>

And now the "magic": I changed Position_pod to Position

struct text_descriptor {
    int             id;
    Position_pod    pos;
    const int       &constNum;
};

becomes this:

struct text_descriptor {
    int             id;
    Position        pos;
    const int       &constNum;
};

and now it gives the expected error:

error: uninitialized const member ‘text_descriptor::constNum'

My question: Why it compiles in the first case, when it should give an error (as in the second case). The difference is, that Position_pod uses C - style brace initialization and Position uses C++11 - style initialization, which call Position's constructor. But how does this affect the possibility to leave a reference member uninitialized?

(Update) Compiler: gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

It will be clear that

 struct text_descriptor td[3] = { {0, {465,223}, 123}, {1, {465,262}, 123}, }; 

is list-initialisation, and that the initialiser list is not empty.

C++11 says ([dcl.init.list]p3):

List-initialization of an object or reference of type T is defined as follows:

  • If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
  • Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).
  • ...

[dcl.init.aggr]p1:

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

td is an array, so it is an aggregate, so aggregate initialisation is performed.

[dcl.init.aggr]p7:

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from an empty initializer list (8.5.4).

This is the case here, so td[2] is initialised from an empty initialiser list, which ([dcl.init.list]p3 again) means it is value-initialised.

Value-initialisation, in turn, means ([dcl.init]p7):

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), ...
  • 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.
  • ...

Your class text_descriptor is a class with no user-provided constructor, so td[2] is first zero-initialised, and then its constructor is called.

Zero-initialisation means ([dcl.init]p5):

To zero-initialize an object or reference of type T means:

  • if T is a scalar type (3.9), ...
  • if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
  • if T is a (possibly cv-qualified) union type, ...
  • if T is an array type, ...
  • if T is a reference type, no initialization is performed.

This is well-defined regardless of text_descriptor 's default constructor: it just zero-initialises the non-reference members and sub-members.

Then the default constructor is called, if it is non-trivial. Here's how the default constructor is defined ([special]p5):

A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared constructor for class X , a constructor having no parameters is implicitly declared as defaulted (8.4). An implicitly-declared default constructor is an inline public member of its class. A defaulted default constructor for class X is defined as deleted if:

  • ...
  • any non-static data member with no brace-or-equal-initializer is of reference type,
  • ...

A default constructor is trivial if it is not user-provided and if:

  • its class has no virtual functions (10.3) and no virtual base classes (10.1), and
  • no non-static data member of its class has a brace-or-equal-initializer, and
  • all the direct base classes of its class have trivial default constructors, and
  • for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.

Otherwise, the default constructor is non-trivial.

So, the implicitly defined constructor is deleted, as expected, but it is also trivial, if pos is a POD type (!). Because the constructor is trivial, it is not called. Because the constructor is not called, the fact that it is deleted is not a problem.

This is a gaping hole in C++11, which has since been fixed. It happened to have been fixed to deal with inaccessible trivial default constructors , but the fixed wording also covers deleted trivial default constructors. N4140 (roughly C++14) says in [dcl.init.aggr]p7 (emphasis mine):

  • 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;

As TC pointed out in the comments, another DR also changed so that td[2] is still initialised from an empty initialiser list, but that empty initialiser list now implies aggregate initialisation. That, in turn, implies each of td[2] 's members is initialised from an empty initialiser list as well ([dcl.init.aggr]p7 again), so would seem to initialise the reference member from {} .

[dcl.init.aggr]p9 then says (as remyabel had pointed out in a now-deleted answer):

If an incomplete or empty initializer-list leaves a member of reference type uninitialized, the program is ill-formed.

It is unclear to me that this applies to references initialised from the implicit {} , but compilers do interpret it as such, and there's not much else that could be meant by it.

The first version (the one with _pod suffix) still works but gives no error because for the z value, the default value of int is chosen (the 0). Idem for the const int reference.

In the second version, you cannot define a const reference without giving it a value. The compiler gives you an error because later you cannot assign any value to it.

In addition, the compiler you're using plays an important role here, maybe it's a bug, just because you are declaring a class member before an int member.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM