I have a code similar to this:
class AClass {
public:
struct AStruct { };
AClass(){}
private:
const AStruct m_struct;
};
int main() {
AClass a;
}
It throws this compilation error (with Clang LLVM version 5.1):
error: constructor for 'AClass' must explicitly initialize
the const member 'm_struct'
If I specify a C++11 default constructor for struct AStruct, I get the same error:
struct AStruct {
AStruct() = default;
};
However, this is solved by writing a constructor with an empty body:
struct AStruct {
AStruct(){} // fixed
};
Why do I need to specify an empty constructor? Isn't it automatically created with public access for structs?
Why does not the C++11 default constructor solve the problem?
From §8.5 [dcl.init]/7:
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.
The default constructor of AClass default-initializes the const member (see below), so that member must have a user-provided default constructor. Using = default does not result in a user-provided default constructor, as can be seen in §8.4.2 [dcl.fct.def.default]/4:
A function is user-provided if it is user-declared and not explicitly defaulted or
deleted on its first declaration.
The member is default-initialized per §12.6.2 [class.base.init]/8:
In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then
— if the entity is a non-static data member that has a brace-or-equal-initializer , the entity is initialized as specified in 8.5;
— otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
— otherwise, the entity is default-initialized (8.5).
Stolen from #chris's answer we have this paragraph: §8.5 [dcl.init]/7:
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.
We can then construct a completely ridiculous case that illustrates this restriction:
struct Foo {};
int main() {
const Foo f;
}
which fails to compile in clang, as the standard dictates. Your code is simply this, but as a member variable of another class/struct.
We can even do this:
struct Foo {int x = 3;};
int main() {
const Foo f;
}
where all the data is obviously initialized. This last example convinces me that this is a defect in the standard.
The thought was probably something to do with POD types being uninitialized and const, but the wording blocks code that is unrelated. Default constructors in modern C++ are often more than good enough, and forcing Foo(){} is poor form.
Related
Example for code:
class Dog
{
private:
int x;
public:
Dog()=default;
};
Vs. this code:
class Dog
{
private:
int x;
};
What is the difference between the constructor that is "=default" (the first code) and the constructor that the compiler creates (like the second code)?
Dog() = default; is a user declared constructor (not to be confused with a user defined constructor). It is a defaulted default constructor. Typically you would use it when the class has other constructors but you still want the compiler to generate a default constructor (or rather a "defaulted default constructor". This is C++ terminology at its best. Note how the two "default"s have slightly different meaning).
A user declared constructor prevents a class from being an aggregate. From cppreference, only for C++20:
An aggregate is one of the following types:
array type
class type (typically, struct or union), that has
no private or protected direct non-static data members
no user-declared or inherited constructors
no virtual, private, or protected base classes
no virtual member functions
As an example, consider:
#include <iostream>
#include <type_traits>
class Dog {
int x;
public:
Dog()=default;
};
class Horse {
int x;
};
class Swan {
public:
int x;
};
class Cow {
public:
int x;
Cow() = default;
};
int main() {
std::cout << std::is_aggregate_v<Dog>;
std::cout << std::is_aggregate_v<Horse>;
std::cout << std::is_aggregate_v<Swan>;
std::cout << std::is_aggregate_v<Cow>;
}
Output
0010
The first two, Dog and Horse, resemble your two versions of Dog. They are not aggregates, because they have private members. Swan is an aggregate, but Cow is not, because it has a user declared constructor.
Something that works with aggregates, but not with non-aggregates, is designated initializers (same cppreference page):
Swan s{.x=3}; // OK
Cow c{.x=4}; // Error: Cow is not an aggregate
TL;DR: I am not aware of a difference between your two Dogs, but in general the presence of a user declared constructor can make a difference.
I'll limit the scope to a default constructor, as in the question's code and tag. For the most part, you'll get the same effect since = default; loosely means "give me the compiler-generated one". It's important to note what exactly having no declaration does:
If there is
no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared
as defaulted. An implicitly-declared default constructor is an inline public member of its class.
If your declaration changes any of these, it will no longer be the exact same as the implicit one. In the standard, Dog() = default; is a user-declared constructor, but not a user-provided constructor. There are a couple small differences between having a user-declared default constructor and having no constructor.
As mentioned, aggregates were fixed:
struct not_agg {
not_agg() = delete;
int x;
};
Before the fix, such a class could be created via aggregate initialization: not_agg{}. Naturally, this also extends to = default;. Per [dcl.init.aggr]:
An aggregate is an array or a class with
no user-declared or inherited constructors
There is also rationale given for this change in annex C:
Remove potentially error-prone aggregate initialization which may apply notwithstanding the
declared constructors of a class.
An interesting, but very tiny difference is that a class with a user-declared constructor is not allowed to have non-static data members of the same name as the class:
class c {
int c; // Okay
};
class c2 {
c2() = default;
int c2; // Error
};
This is due to [class.mem]/21:
In addition, if class T has a user-declared constructor, every non-static data member of class T shall have a name different from T.
Default declared constructors can be guarded with protected or private access. Not declared default constructors are always inline public members of their classes.
= default constructors are instantiated like usual member functions, may be inline or not, they have strong linkage references in the second case. The compiler creates not declared default constructors as inline member functions with weak linkage references.
As mentioned in the comments, =default constructors are user-defined constructors and their classes are not aggregate types.
The naive, optimistic and oh.. so wrong view of the c++11 uniform initialization syntax
I thought that since C++11 user-defined type objects should be constructed with the new {...} syntax instead of the old (...) syntax (except for constructor overloaded for std::initializer_list and similar parameters (e.g. std::vector: size ctor vs 1 elem init_list ctor)).
The benefits are: no narrow implicit conversions, no problem with the most vexing parse, consistency(?). I saw no problem as I thought they are the same (except the example given).
But they are not.
A tale of pure madness
The {} calls the default constructor.
... Except when:
the default constructor is deleted and
there are no other constructors defined.
Then it looks like it it rather value initializes the object?... Even if the object has deleted default constructor, the {} can create an object. Doesn't this beat the whole purpose of a deleted constructor?
...Except when:
the object has a deleted default constructor and
other constructor(s) defined.
Then it fails with call to deleted constructor.
...Except when:
the object has a deleted constructor and
no other constructor defined and
at least a non-static data member.
Then it fails with missing field initializers.
But then you can use {value} to construct the object.
Ok maybe this is the same as the first exception (value init the object)
...Except when:
the class has a deleted constructor
and at least one data members in-class default initialized.
Then nor {} nor {value} can create an object.
I am sure I missed a few. The irony is that it is called uniform initialization syntax. I say again: UNIFORM initialization syntax.
What is this madness?
Scenario A
Deleted default constructor:
struct foo {
foo() = delete;
};
// All bellow OK (no errors, no warnings)
foo f = foo{};
foo f = {};
foo f{}; // will use only this from now on.
Scenario B
Deleted default constructor, other constructors deleted
struct foo {
foo() = delete;
foo(int) = delete;
};
foo f{}; // OK
Scenario C
Deleted default constructor, other constructors defined
struct foo {
foo() = delete;
foo(int) {};
};
foo f{}; // error call to deleted constructor
Scenario D
Deleted default constructor, no other constructors defined, data member
struct foo {
int a;
foo() = delete;
};
foo f{}; // error use of deleted function foo::foo()
foo f{3}; // OK
Scenario E
Deleted default constructor, deleted T constructor, T data member
struct foo {
int a;
foo() = delete;
foo(int) = delete;
};
foo f{}; // ERROR: missing initializer
foo f{3}; // OK
Scenario F
Deleted default constructor, in-class data member initializers
struct foo {
int a = 3;
foo() = delete;
};
/* Fa */ foo f{}; // ERROR: use of deleted function `foo::foo()`
/* Fb */ foo f{3}; // ERROR: no matching function to call `foo::foo(init list)`
When viewing things this way it is easy to say there is complete and utter chaos in the way an object is initialized.
The big difference comes from the type of foo: if it is an aggregate type or not.
It is an aggregate if it has:
no user-provided constructors (a deleted or defaulted function does not count as user-provided),
no private or protected non-static data members,
no brace-or-equal-initializers for non-static data members (since c++11 until (reverted in) c++14)
no base classes,
no virtual member functions.
So:
in scenarios A B D E: foo is an aggregate
in scenarios C: foo is not an aggregate
scenario F:
in c++11 it is not an aggregate.
in c++14 it is an aggregate.
g++ hasn't implemented this and still treats it as a non-aggregate even in C++14.
4.9 doesn't implement this.
5.2.0 does
5.2.1 ubuntu doesn't (maybe a regression)
The effects of list initialization of an object of type T are:
...
If T is an aggregate type, aggregate initialization is performed. This takes care of scenarios A B D E (and F in C++14)
Otherwise the constructors of T are considered in two phases:
All constructors that take std::initializer_list ...
otherwise [...] all constructors of T participate in overload resolution [...] This takes care of C (and F in C++11)
...
:
Aggregate initialization of an object of type T (scenarios A B D E (F c++14)):
Each non-static class member, in order appearance in the class definition, is copy-initialized from the corresponding clause of the
initializer list. (array reference omitted)
TL;DR
All these rules can still seem very complicated and headache inducing. I personally over-simplify this for myself (if I thereby shoot myself in the foot then so be it: I guess I will spend 2 days in the hospital rather than having a couple of dozen days of headaches):
for an aggregate each data member is initialized from the elements of the list initializer
else call constructor
Doesn't this beat the whole purpose of a deleted constructor?
Well, I don't know about that, but the solution is to make foo not an aggregate. The most general form that adds no overhead and doesn't change the used syntax of the object is to make it inherit from an empty struct:
struct dummy_t {};
struct foo : dummy_t {
foo() = delete;
};
foo f{}; // ERROR call to deleted constructor
In some situations (no non-static members at all, I guess), an alternate would be to delete the destructor (this will make the object not instantiable in any context):
struct foo {
~foo() = delete;
};
foo f{}; // ERROR use of deleted function `foo::~foo()`
This answer uses information gathered from:
C++14 value-initialization with deleted constructor
What are Aggregates and PODs and how/why are they special?
List initialization
Aggregate initialization
Direct initialization
Many thanks to #M.M who helped correct and improve this post.
What's messing you up is aggregate initialization.
As you say, there are benefits and drawbacks to using list initialization. (The term "uniform initialization" is not used by the C++ Standard).
One of the drawbacks is that list initialization behaves differently for aggregates than non-aggregates. Also, the definition of aggregate changes slightly with each Standard.
Aggregates are not created via a constructor. (Technically they actually might be, but this is a good way to think of it). Instead, when creating an aggregate, memory is allocated and then each member is initialized in order according to what's in the list initializer.
Non-aggregates are created via constructors, and in that case the members of the list initializer are constructor arguments.
There is actually a design flaw in the above: if we have T t1; T t2{t1};, then the intent is to perform copy-construction. However, (prior to C++14) if T is an aggregate then aggregate initialization happens instead, and t2's first member is initialized with t1.
This flaw was fixed in a defect report which modified C++14, so from now on, copy-construction is checked for before we move onto aggregate initialization.
The definition of aggregate from C++14 is:
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).
In C++11, a default value for a non-static member meant a class was not an aggregate; however that was changed for C++14. User-provided means user-declared , but not = default or = delete.
If you want to make sure that your constructor call never accidentally performs aggregate initialization, then you have to use ( ) rather than { }, and avoid MVPs in other ways.
These cases around aggregate initialization are counter-intuitive for most and was the subject of the proposal p1008: Prohibit aggregates with user-declared constructors which says:
C++ currently allows some types with user-declared constructors to be initialized via aggregate
initialization, bypassing those constructors. The result is code that is surprising, confusing, and
buggy. This paper proposes a fix that makes initialization semantics in C++ safer, more uniform,
and easier to teach. We also discuss the breaking changes that this fix introduces
and introduces some examples, which overlap nicely with the cases you present:
struct X {
X() = delete;
};
int main() {
X x1; // ill-formed - default c’tor is deleted
X x2{}; // compiles!
}
Clearly, the intent of the deleted constructor is to prevent the user from initializing the class. However, contrary to intuition, this does not work: the user can still initialize X
via aggregate initialization because this completely bypasses the constructors. The author could even explicitly delete all of default, copy, and move constructor, and still fail to prevent the client code from instantiating X via aggregate initialization as above. Most C++ developers are surprised by the
current behaviour when shown this code
The author of class X could alternatively consider making the default constructor
private. But if
this constructor is given a defaulted definition, this again does not prevent aggregate initialization (and thus, instantiation) of the class:
struct X {
private:
X() = default;
};
int main() {
X x1; // ill-formed - default c’tor is private
X x2{}; // compiles!
}
Because of the current rules, aggregate initialization allows us to “default-construct” a class even if it is not, in fact, default-constructible:
static_assert(!std::is_default_constructible_v<X>);
would pass for both definitions of X above.
...
The proposed changes are:
Modify [dcl.init.aggr] paragraph 1 as follows:
An aggregate is an array or a class (Clause 12) with
no user-provided, explicit, u̲s̲e̲r̲-̲d̲e̲c̲l̲a̲r̲e̲d̲ or inherited
constructors (15.1),
no private or protected non-static data members (Clause 14),
no virtual functions (13.3), and
no virtual, private, or protected base classes (13.1).
Modify [dcl.init.aggr] paragraph 17 as follows:
[Note: An aggregate array or an aggregate class may contain elements of a class >>type with a user-provided u̲s̲e̲r̲-̲d̲e̲c̲l̲a̲r̲e̲d̲ constructor (15.1). Initialization of >>these aggregate objects is described in 15.6.1. —end note]
Add the following to [diff.cpp17] in Annex C, section C.5 C++ and ISO C++ 2017:
C.5.6 Clause 11: declarators [diff.cpp17.dcl.decl]
Affected subclause: [dcl.init.aggr]
Change: A class that has
user-declared constructors is never an aggregate.
Rationale: Remove
potentially error-prone aggregate initialization which may apply
not withstanding the declared constructors of a class.
Effect on original feature: Valid C++ 2017 code that aggregate-initializes a
type with a user-declared constructor may be ill-formed or have
different semantics in this International Standard.
Followed by examples which I omit.
The proposal was accepted and merged into C++20 we can find the latest draft here which contains these changes and we can see the changes to [dcl.init.aggr]p1.1 and [dcl.init.aggr]p17 and C++17 declarations diff.
So this should be fixed in C++20 forward.
The naive, optimistic and oh.. so wrong view of the c++11 uniform initialization syntax
I thought that since C++11 user-defined type objects should be constructed with the new {...} syntax instead of the old (...) syntax (except for constructor overloaded for std::initializer_list and similar parameters (e.g. std::vector: size ctor vs 1 elem init_list ctor)).
The benefits are: no narrow implicit conversions, no problem with the most vexing parse, consistency(?). I saw no problem as I thought they are the same (except the example given).
But they are not.
A tale of pure madness
The {} calls the default constructor.
... Except when:
the default constructor is deleted and
there are no other constructors defined.
Then it looks like it it rather value initializes the object?... Even if the object has deleted default constructor, the {} can create an object. Doesn't this beat the whole purpose of a deleted constructor?
...Except when:
the object has a deleted default constructor and
other constructor(s) defined.
Then it fails with call to deleted constructor.
...Except when:
the object has a deleted constructor and
no other constructor defined and
at least a non-static data member.
Then it fails with missing field initializers.
But then you can use {value} to construct the object.
Ok maybe this is the same as the first exception (value init the object)
...Except when:
the class has a deleted constructor
and at least one data members in-class default initialized.
Then nor {} nor {value} can create an object.
I am sure I missed a few. The irony is that it is called uniform initialization syntax. I say again: UNIFORM initialization syntax.
What is this madness?
Scenario A
Deleted default constructor:
struct foo {
foo() = delete;
};
// All bellow OK (no errors, no warnings)
foo f = foo{};
foo f = {};
foo f{}; // will use only this from now on.
Scenario B
Deleted default constructor, other constructors deleted
struct foo {
foo() = delete;
foo(int) = delete;
};
foo f{}; // OK
Scenario C
Deleted default constructor, other constructors defined
struct foo {
foo() = delete;
foo(int) {};
};
foo f{}; // error call to deleted constructor
Scenario D
Deleted default constructor, no other constructors defined, data member
struct foo {
int a;
foo() = delete;
};
foo f{}; // error use of deleted function foo::foo()
foo f{3}; // OK
Scenario E
Deleted default constructor, deleted T constructor, T data member
struct foo {
int a;
foo() = delete;
foo(int) = delete;
};
foo f{}; // ERROR: missing initializer
foo f{3}; // OK
Scenario F
Deleted default constructor, in-class data member initializers
struct foo {
int a = 3;
foo() = delete;
};
/* Fa */ foo f{}; // ERROR: use of deleted function `foo::foo()`
/* Fb */ foo f{3}; // ERROR: no matching function to call `foo::foo(init list)`
When viewing things this way it is easy to say there is complete and utter chaos in the way an object is initialized.
The big difference comes from the type of foo: if it is an aggregate type or not.
It is an aggregate if it has:
no user-provided constructors (a deleted or defaulted function does not count as user-provided),
no private or protected non-static data members,
no brace-or-equal-initializers for non-static data members (since c++11 until (reverted in) c++14)
no base classes,
no virtual member functions.
So:
in scenarios A B D E: foo is an aggregate
in scenarios C: foo is not an aggregate
scenario F:
in c++11 it is not an aggregate.
in c++14 it is an aggregate.
g++ hasn't implemented this and still treats it as a non-aggregate even in C++14.
4.9 doesn't implement this.
5.2.0 does
5.2.1 ubuntu doesn't (maybe a regression)
The effects of list initialization of an object of type T are:
...
If T is an aggregate type, aggregate initialization is performed. This takes care of scenarios A B D E (and F in C++14)
Otherwise the constructors of T are considered in two phases:
All constructors that take std::initializer_list ...
otherwise [...] all constructors of T participate in overload resolution [...] This takes care of C (and F in C++11)
...
:
Aggregate initialization of an object of type T (scenarios A B D E (F c++14)):
Each non-static class member, in order appearance in the class definition, is copy-initialized from the corresponding clause of the
initializer list. (array reference omitted)
TL;DR
All these rules can still seem very complicated and headache inducing. I personally over-simplify this for myself (if I thereby shoot myself in the foot then so be it: I guess I will spend 2 days in the hospital rather than having a couple of dozen days of headaches):
for an aggregate each data member is initialized from the elements of the list initializer
else call constructor
Doesn't this beat the whole purpose of a deleted constructor?
Well, I don't know about that, but the solution is to make foo not an aggregate. The most general form that adds no overhead and doesn't change the used syntax of the object is to make it inherit from an empty struct:
struct dummy_t {};
struct foo : dummy_t {
foo() = delete;
};
foo f{}; // ERROR call to deleted constructor
In some situations (no non-static members at all, I guess), an alternate would be to delete the destructor (this will make the object not instantiable in any context):
struct foo {
~foo() = delete;
};
foo f{}; // ERROR use of deleted function `foo::~foo()`
This answer uses information gathered from:
C++14 value-initialization with deleted constructor
What are Aggregates and PODs and how/why are they special?
List initialization
Aggregate initialization
Direct initialization
Many thanks to #M.M who helped correct and improve this post.
What's messing you up is aggregate initialization.
As you say, there are benefits and drawbacks to using list initialization. (The term "uniform initialization" is not used by the C++ Standard).
One of the drawbacks is that list initialization behaves differently for aggregates than non-aggregates. Also, the definition of aggregate changes slightly with each Standard.
Aggregates are not created via a constructor. (Technically they actually might be, but this is a good way to think of it). Instead, when creating an aggregate, memory is allocated and then each member is initialized in order according to what's in the list initializer.
Non-aggregates are created via constructors, and in that case the members of the list initializer are constructor arguments.
There is actually a design flaw in the above: if we have T t1; T t2{t1};, then the intent is to perform copy-construction. However, (prior to C++14) if T is an aggregate then aggregate initialization happens instead, and t2's first member is initialized with t1.
This flaw was fixed in a defect report which modified C++14, so from now on, copy-construction is checked for before we move onto aggregate initialization.
The definition of aggregate from C++14 is:
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).
In C++11, a default value for a non-static member meant a class was not an aggregate; however that was changed for C++14. User-provided means user-declared , but not = default or = delete.
If you want to make sure that your constructor call never accidentally performs aggregate initialization, then you have to use ( ) rather than { }, and avoid MVPs in other ways.
These cases around aggregate initialization are counter-intuitive for most and was the subject of the proposal p1008: Prohibit aggregates with user-declared constructors which says:
C++ currently allows some types with user-declared constructors to be initialized via aggregate
initialization, bypassing those constructors. The result is code that is surprising, confusing, and
buggy. This paper proposes a fix that makes initialization semantics in C++ safer, more uniform,
and easier to teach. We also discuss the breaking changes that this fix introduces
and introduces some examples, which overlap nicely with the cases you present:
struct X {
X() = delete;
};
int main() {
X x1; // ill-formed - default c’tor is deleted
X x2{}; // compiles!
}
Clearly, the intent of the deleted constructor is to prevent the user from initializing the class. However, contrary to intuition, this does not work: the user can still initialize X
via aggregate initialization because this completely bypasses the constructors. The author could even explicitly delete all of default, copy, and move constructor, and still fail to prevent the client code from instantiating X via aggregate initialization as above. Most C++ developers are surprised by the
current behaviour when shown this code
The author of class X could alternatively consider making the default constructor
private. But if
this constructor is given a defaulted definition, this again does not prevent aggregate initialization (and thus, instantiation) of the class:
struct X {
private:
X() = default;
};
int main() {
X x1; // ill-formed - default c’tor is private
X x2{}; // compiles!
}
Because of the current rules, aggregate initialization allows us to “default-construct” a class even if it is not, in fact, default-constructible:
static_assert(!std::is_default_constructible_v<X>);
would pass for both definitions of X above.
...
The proposed changes are:
Modify [dcl.init.aggr] paragraph 1 as follows:
An aggregate is an array or a class (Clause 12) with
no user-provided, explicit, u̲s̲e̲r̲-̲d̲e̲c̲l̲a̲r̲e̲d̲ or inherited
constructors (15.1),
no private or protected non-static data members (Clause 14),
no virtual functions (13.3), and
no virtual, private, or protected base classes (13.1).
Modify [dcl.init.aggr] paragraph 17 as follows:
[Note: An aggregate array or an aggregate class may contain elements of a class >>type with a user-provided u̲s̲e̲r̲-̲d̲e̲c̲l̲a̲r̲e̲d̲ constructor (15.1). Initialization of >>these aggregate objects is described in 15.6.1. —end note]
Add the following to [diff.cpp17] in Annex C, section C.5 C++ and ISO C++ 2017:
C.5.6 Clause 11: declarators [diff.cpp17.dcl.decl]
Affected subclause: [dcl.init.aggr]
Change: A class that has
user-declared constructors is never an aggregate.
Rationale: Remove
potentially error-prone aggregate initialization which may apply
not withstanding the declared constructors of a class.
Effect on original feature: Valid C++ 2017 code that aggregate-initializes a
type with a user-declared constructor may be ill-formed or have
different semantics in this International Standard.
Followed by examples which I omit.
The proposal was accepted and merged into C++20 we can find the latest draft here which contains these changes and we can see the changes to [dcl.init.aggr]p1.1 and [dcl.init.aggr]p17 and C++17 declarations diff.
So this should be fixed in C++20 forward.
suppose I define a copy c'tor for a class B that inherits A and has a also member variables.
Inside the copt c'tor body I write some code, but in the initiallization list I don't call explicitly to A c'tor(also not to copy c'tor), and not initiallize the member variables.
In this case A default c'tor will be called defaultly before reaching B' copy c'tor body.
But what would be with the member variables?
would they be initillized with their default c'tor or with their copy c'tor with the arguement object's members (the argument object == given to B copy c'tor).
In addition, if there is calling inside the initiallization list to some members' copy c'tor/c'tor or to Parent class copy c'tor/c'tor - would that behaviour be changed?
I know that in case we don't define explicitly copy c'tor: Parent Class and members copy c'tor's are called.
What should I expect in here?
The base class subobject will be default initialized in that case. See [class.base.init]/8:
In a non-delegating constructor, if a given potentially constructed
subobject is not designated by a mem-initializer-id (including the
case where there is no mem-initializer-list because the constructor
has no ctor-initializer), then
if the entity is a non-static data member that has a brace-or-equal-initializer [..]
otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
otherwise, the entity is default-initialized (8.5).
And a potentially constructed subobject is defined in [special]/5:
For a class, its non-static data members, its non-virtual direct base
classes, and, if the class is not abstract (10.4), its virtual base
classes are called its potentially constructed subobjects.
If you write a copy-constructor and do not initialize a member variable then it will be default-initialized.
The same applies to base classes, in most ways they are treated the same as member variables.
Here is some sample code that will demonstrate:
#include <iostream>
using namespace std;
struct S
{
S() { cout << "S()\n"; }
S(S const &) { cout << "S(S&)\n"; }
};
struct T : S
{
T() {}
T(T const &t) {}
// you have to explicitly write this if you want it
// T(T const &t): S(t) {}
// ^^^^
};
int main()
{
T t;
T u(t);
}
I have a structure which I create a custom constructor to initialize the members to 0's. I've seen in older compilers that when in release mode, without doing a memset to 0, the values are not initialized.
I now want to use this structure in a union, but get errors because it has a non-trivial constructor.
So, question 1. Does the default compiler implemented constructor guarantee that all members of a structure will be null initialized? The non-trivial constructor just does a memset of all the members to '0' to ensure a clean structure.
Question 2: If a constructor must be specified on the base structure, how can a union be implemented to contain that element and ensure a 0 initialized base element?
Question 1: Default constructors do initialize POD members to 0 according to the C++ standard. See the quoted text below.
Question 2: If a constructor must be specified in a base class, then that class cannot be part of a union.
Finally, you can provide a constructor for your union:
union U
{
A a;
B b;
U() { memset( this, 0, sizeof( U ) ); }
};
For Q1:
From C++03, 12.1 Constructors, pg 190
The implicitly-defined default constructor performs the set of initializations of the
class that would be performed by a user-written default constructor for that class with an empty mem-initializer-list (12.6.2) and an empty function body.
From C++03, 8.5 Initializers, pg 145
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);
if T is an array type, each element is default-initialized;
otherwise, the object is zero-initialized.
To zero-initialize an object of type T means:
if T is a scalar type (3.9), the object is set to the value of 0 (zero) converted to T;
if T is a non-union class type, each non static data member and each base-class subobject is zero-initialized;
if T is a union type, the object’s first named data member is zero-initialized;
if T is an array type, each element is zero-initialized;
if T is a reference type, no initialization is performed.
For Q2:
From C++03, 12.1 Constructors, pg 190
A constructor is trivial if it is an implicitly-declared default constructor and if:
its class has no virtual functions (10.3) and no virtual base classes (10.1), and
all the direct base classes of its class have trivial constructors, and
for all the nonstatic data members of its class that are of class type (or array
thereof), each such class has a trivial constructor
From C++03, 9.5 Unions, pg 162
A union can have member functions (including constructors and destructors), but not virtual (10.3) functions. A union shall not have base classes. A union shall not be used as a base class.An object of a class with a non-trivial constructor (12.1), a non-trivial copy constructor (12.8), a non-trivial destructor (12.4), or a non-trivial copy assignment operator (13.5.3, 12.8) cannot be a member of a union, nor can an array of such objects
Things changed for the better in C++11.
You can now legally do this, as described by Stroustrup himself (I reached that link from the Wikipedia article on C++11).
The example on Wikipedia is as follows:
#include <new> // Required for placement 'new'.
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p; // Illegal in C++03; legal in C++11.
U() {new(&p) Point();} // Due to the Point member, a constructor
// definition is now *required*.
};
Stroustrup goes into a little more detail.
AFAIK union members may not have constructors or destructors.
Question 1: no, there's no such guarantee. Any POD-member not in the constructor's initialization list gets default-initialized, but that's with a constructor you define, and has an initializer list. If you don't define a constructor, or you define a constructor without an initializer list and empty body, POD-members will not be initialized.
Non-POD members will always be constructed via their default constructor, which if synthesized, again would not initialize POD-members. Given that union members may not have constructors, you'd pretty much be guaranteed that POD-members of structs in a union will not be initialized.
Question 2: you can always initialize structures/unions like so:
struct foo
{
int a;
int b;
};
union bar
{
int a;
foo f;
};
bar b = { 0 };
As mentioned in Greg Rogers' comment to unwesen's post, you can give your union a constructor (and destructor if you wish):
struct foo
{
int a;
int b;
};
union bar
{
bar() { memset(this, 0, sizeof(*this)); }
int a;
foo f;
};
Can you do something like this?
class Outer
{
public:
Outer()
{
memset(&inner_, 0, sizeof(inner_));
}
private:
union Inner
{
int qty_;
double price_;
} inner_;
};
...or maybe something like this?
union MyUnion
{
int qty_;
double price_;
};
void someFunction()
{
MyUnion u = {0};
}
You'll have to wait for C++0x to be supported by compilers to get this. Until then, sorry.