Member initialisation and construction

T

Taras_96

Hi all,

I'm trying to determine which members get intialised by default and
which don't when we consider both constructors and copy constructors.
Essentially I have the following matrix (I abbreviate Bjarne
Stroustrup's C++ book as 'TCPL' below)

Constructor:
Compiler generated default
UDTs – default initialised – page 243 of TCPL
Built in – not default initialised – page 244 of TCPL
User generated
UDTs – default initialised – page 248 of TCPL (the example shows a
UDT of a user generated constructor being default initialised)
Built in - not sure about this
Copy constructor
Compiler generated default
UDTs - not sure about this - would they be copied using the UDT's
copy constructor?
Built in - not sure about this
User generated
UDTs – default initialised – page 250 of TCPL
Built in - not sure about this

Can anyone fill in the missing entries? ;)

Taras
 
B

Bart van Ingen Schenau

Hi all,

I'm trying to determine which members get intialised by default and
which don't when we consider both constructors and copy constructors.
Essentially I have the following matrix (I abbreviate Bjarne
Stroustrup's C++ book as 'TCPL' below)

Constructor:
        Compiler generated default
                UDTs – default initialised – page 243 of TCPL
                Built in – not default initialised – page 244 of TCPL
        User generated
                UDTs – default initialised – page 248 of TCPL (the example shows a
UDT of a  user generated constructor being default initialised)
                Built in - not sure about this
Copy constructor
        Compiler generated default
                UDTs - not sure about this - would they be copied using the UDT's
copy constructor?
                Built in - not sure about this
        User generated
                UDTs – default initialised – page 250 of TCPL
                Built in - not sure about this

Can anyone fill in the missing entries? ;)

Taras

First, the distinction you make between User-Defined types and built-
in types does not cover all cases. There are a number of user-defined
types (such as simple structures of built-in types) that act similar
to a built-in type in your table.

A better distinction is if your members have a trivial or non-trivial
consctructor.
If a type or one of its (indirect) members or baseclasses has a user-
defined constructor, then the type has a non-trivial constructor. All
other types have have a non-trivial constructor.

With this distinction, your table becomes:
default constructor:
compiler-generated default:
non-trivial constructor: default constructor is called
trivial constructor: member is left uninitialised
user generated:
non-trivial constructor: if mentioned in the initialiser-list, the
corresponding constructor is called; otherwise the default constructor
is called
trivial constructor: if the member is mentioned in the initialiser-
list, it is initialised; otherwise the member is left uninitialised
copy-constructor:
compiler-generated default:
non-trivial constructor: copy-constructor is called
trivial constructor: member is copied
user generated:
non-trivial constructor: if mentioned in the initialiser-list, the
corresponding constructor is called; otherwise the default constructor
is called
trivial constructor: if the member is mentioned in the initialiser-
list, it is initialised; otherwise the member is left uninitialised

Bart v Ingen Schenau
 
T

Taras_96

On Oct 20, 8:55 am, Taras_96 <[email protected]> wrote:
  compiler-generated default:
    non-trivial constructor: copy-constructor is called
    trivial constructor: member is copied
  user generated:
    non-trivial constructor: if mentioned in the initialiser-list, the
corresponding constructor is called; otherwise the default constructor
is called
    trivial constructor: if the member is mentioned in the initialiser-
list, it is initialised; otherwise the member is left uninitialised

Bart v Ingen Schenau

Does this mean if you have the following classes:

class A{};

class B
{
private:
A a;
public:
B(const & rhs){}
}

Then the member 'a' would be left unitialised, even though it is a
UDT?

Taras
 
V

Victor Bazarov

Taras_96 said:
Does this mean if you have the following classes:

class A{};

class B
{
private:
A a;
public:
B(const & rhs){}
}

Then the member 'a' would be left unitialised, even though it is a
UDT?

First off, your code has no semicolon after the definition of 'B' and
the argument in B's c-tor has no type. If that's fixed, then the rule
is simple: any data member omitted from the initialiser list, shall be
value-initialised ([class.base.init]/3), not *un*initialised. UDT or
not does not matter. Value-initialisation is performed differently for
POD than for non-POD, but that does not change the fact that an omitted
member is value-initialised.

V
 
T

Taras_96

Does this mean if you have the following classes:
class A{};
class B
{
  private:
    A a;
  public:
    B(const & rhs){}
}
Then the member 'a' would be left unitialised, even though it is a
UDT?

First off, your code has no semicolon after the definition of 'B' and
the argument in B's c-tor has no type.  If that's fixed, then the rule
is simple: any data member omitted from the initialiser list, shall be
value-initialised ([class.base.init]/3), not *un*initialised.  UDT or
not does not matter.  Value-initialisation is performed differently for
POD than for non-POD, but that does not change the fact that an omitted
member is value-initialised.

V
--

I might be reading an old copy of the standard, but the version I'm
reading (http://www.kuzbass.ru:8086/docs/isocpp) states:

class.base.init/3:

"The expression-list in a mem-initializer is used to initialize the
base class or nonstatic data member subobject denoted by the mem-
initializer-id. The semantics of a mem-initializer are as follows:
* if the expression-list of the mem-initializer is omitted, the
base class or member subobject is default-initialized (see dcl.init);
* otherwise, the subobject indicated by mem-initializer-id is
direct-initialized using expression-list as the initializer (see
dcl.init). "

So this means that:

class Bar
{
public:
~Bar() {}; // so that Bar is not POD
};

class Foo
{
private:
int x;
Bar bar;
public:
Foo();
~Foo() {};
};

Foo::Foo():
x(), // default initialised
bar() // default initialised
{}


class.base.init/4 states that:

"-4- If a given nonstatic data member or base class is not named 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 nonstatic data member of (possibly cv-
qualified) class type (or array thereof) or a base class, and the
entity class is a non-POD class, the entity is default-initialized
(dcl.init). If the entity is a nonstatic data member of a const-
qualified type, the entity class shall have a user-declared default
constructor.

* Otherwise, the entity is not initialized. If the entity is of
const-qualified type or reference type, or of a (possibly cv-
qualified) POD class type (or array thereof) containing (directly or
indirectly) a member of a const-qualified type, the program is ill-
formed. "

This means that if we changed the above Foo constructor to:

Foo::Foo() {};

Then Foo::bar would be default initialised whereas Foo::x would be
left uninitialised

So what about implicit constructors?

class.ctor.

"-5- 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 default constructor is implicitly declared.
An implicitly-declared default constructor is an inline public member
of its class. A constructor is trivial if it is an implicitly-declared
default constructor and if: ...."

"-7- An implicitly-declared default constructor for a class is
implicitly defined when it is used to create an object of its class
type (intro.object). 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 (class.base.init) and an empty function
body"

So the implicitly-declared default constructor for Foo would be the
same as the Foo constructor declared above... x would be
uninitialised, and bar would be default initialised.

Interestingly there is no mention of the constructor being trivial or
not.

Now what about copy constructors? First consider the implicitly
defined copy constructor...

class.copy

-8- The implicitly-defined copy constructor for class X performs a
memberwise copy of its subobjects. The order of copying is the same as
the order of initialization of bases and members in a user-defined
constructor (see class.base.init). Each subobject is copied in the
manner appropriate to its type:

* if the subobject is of class type, the copy constructor for the
class is used;

* if the subobject is an array, each element is copied, in the
manner appropriate to the element type;

* if the subobject is of scalar type, the built-in assignment
operator is used.

Virtual base class subobjects shall be copied only once by the
implicitly-defined copy constructor (see class.base.init).

What about copy constructors that do no list all members in the member
initialization list?

I believe this would fall under class.base.init/4, where an integer
member would be left unitialised.

However, this doesn't agree with what you stated...?

Taras
 
V

Victor Bazarov

Taras_96 said:
Taras_96 said:
On Oct 20, 7:27 pm, Bart van Ingen Schenau <[email protected]>
wrote:
compiler-generated default:
non-trivial constructor: copy-constructor is called
trivial constructor: member is copied
user generated:
non-trivial constructor: if mentioned in the initialiser-list, the
corresponding constructor is called; otherwise the default constructor
is called
trivial constructor: if the member is mentioned in the initialiser-
list, it is initialised; otherwise the member is left uninitialised
Bart v Ingen Schenau
Does this mean if you have the following classes:
class A{};
class B
{
private:
A a;
public:
B(const & rhs){}
}
Then the member 'a' would be left unitialised, even though it is a
UDT?
First off, your code has no semicolon after the definition of 'B' and
the argument in B's c-tor has no type. If that's fixed, then the rule
is simple: any data member omitted from the initialiser list, shall be
value-initialised ([class.base.init]/3), not *un*initialised. UDT or
not does not matter. Value-initialisation is performed differently for
POD than for non-POD, but that does not change the fact that an omitted
member is value-initialised.

V
--

I might be reading an old copy of the standard, but the version I'm
reading (http://www.kuzbass.ru:8086/docs/isocpp) states:

[.."final draft" of 1998 standard quoted..]

It must be different from the currently active language standard. Get
yourself a copy and check. I vaguely remember that there were some
changes made to initialization clause. Also, look in the archives for
posts from Andrew Koenig on "value-initialization", you will most likely
find that the initialization was reworked/reworded.

V
 
T

Taras_96

Taras_96 said:
Taras_96 wrote:
On Oct 20, 7:27 pm, Bart van Ingen Schenau <[email protected]>
wrote:
  compiler-generated default:
    non-trivial constructor: copy-constructor is called
    trivial constructor: member is copied
  user generated:
    non-trivial constructor: if mentioned in the initialiser-list, the
corresponding constructor is called; otherwise the default constructor
is called
    trivial constructor: if the member is mentioned in the initialiser-
list, it is initialised; otherwise the member is left uninitialised
Bart v Ingen Schenau
Does this mean if you have the following classes:
class A{};
class B
{
  private:
    A a;
  public:
    B(const & rhs){}
}
Then the member 'a' would be left unitialised, even though it is a
UDT?
First off, your code has no semicolon after the definition of 'B' and
the argument in B's c-tor has no type.  If that's fixed, then the rule
is simple: any data member omitted from the initialiser list, shall be
value-initialised ([class.base.init]/3), not *un*initialised.  UDT or
not does not matter.  Value-initialisation is performed differently for
POD than for non-POD, but that does not change the fact that an omitted
member is value-initialised.
V
--
I might be reading an old copy of the standard, but the version I'm
reading (http://www.kuzbass.ru:8086/docs/isocpp) states:
[.."final draft" of 1998 standard quoted..]

It must be different from the currently active language standard.  Get
yourself a copy and check.  I vaguely remember that there were some
changes made to initialization clause.  Also, look in the archives for
posts from Andrew Koenig on "value-initialization", you will most likely
find that the initialization was reworked/reworded.

V

I got the active standard, ISO/IEC 14882:2003(E), and still couldn't
find anywhere that stated what you mentioned:

"then the rule is simple: any data member omitted from the initialiser
list, shall be
value-initialised ([class.base.init]/3), not *un*initialised. UDT or
not does not matter. Value-initialisation is performed differently
for
POD than for non-POD, but that does not change the fact that an
omitted
member is value-initialised."

class.base.init/3 stated:

"— if the expression-list of the mem-initializer is omitted, the base
class or member subobject is value initialized (see 8.5);
— otherwise, the subobject indicated by mem-initializer-id is direct-
initialized using expression-list as the initializer (see 8.5)."

This is concerned with not the mem-initializer-id being absent, but
rather the expression-list being absent.

class.base.init/4 states:

"— If the entity is a nonstatic data member of (possibly cv-qualified)
class type (or array thereof) or a base
class, and the entity class is a non-POD class, the entity is default-
initialized (8.5). If the entity is a nonstatic
data member of a const-qualified type, the entity class shall have a
user-declared default constructor.
— Otherwise, the entity is not initialized. If the entity is of const-
qualified type or reference type, or of a
(possibly cv-qualified) POD class type (or array thereof) containing
(directly or indirectly) a member of
a const-qualified type, the program is ill-formed."

So Foo::x will be unitialized if it is left out in the initialisation
list (and not assigned a value in the body of the constructor).

My interpretation of default versus value initialisation is that the
only difference is that default initialisation calls the default
constructor regardless of whether the ctor is user defined or not,
whereas value-initialisation only calls the default constructor if it
was user defined, otherwise it value-intialises the member.

The standard states that:

"The object is default-initialized if there is no initializer, or
value-initialized if the initializer is ()."

So using the example:

class Foo
{
private:
int x;
public:
~Foo() {};

};

If we write:

Foo x; // default-initialisation

Then the (compiler-generated) constructor will be called, which is
equivalent to an empty constructor with an empty initializer list, so
Foo::x will remain uniitialized.

However,

Foo x() // value-initialisation

Then, as there is no user-defined constructor, the members of x will
be value-initialised, which means that Foo::x will be zero
initialised!

Is this correct?

I'm still unsure of what happens if you do not initialise a data
member in the copy constructor, but I think the above needs to be
sorted first.

Taras
 
P

Pavel

Taras_96 said:
Taras_96 said:
Taras_96 wrote:
On Oct 20, 7:27 pm, Bart van Ingen Schenau <[email protected]>
wrote:
compiler-generated default:
non-trivial constructor: copy-constructor is called
trivial constructor: member is copied
user generated:
non-trivial constructor: if mentioned in the initialiser-list, the
corresponding constructor is called; otherwise the default constructor
is called
trivial constructor: if the member is mentioned in the initialiser-
list, it is initialised; otherwise the member is left uninitialised
Bart v Ingen Schenau
Does this mean if you have the following classes:
class A{};
class B
{
private:
A a;
public:
B(const & rhs){}
}
Then the member 'a' would be left unitialised, even though it is a
UDT?
First off, your code has no semicolon after the definition of 'B' and
the argument in B's c-tor has no type. If that's fixed, then the rule
is simple: any data member omitted from the initialiser list, shall be
value-initialised ([class.base.init]/3), not *un*initialised. UDT or
not does not matter. Value-initialisation is performed differently for
POD than for non-POD, but that does not change the fact that an omitted
member is value-initialised.
V
It must be different from the currently active language standard. Get
yourself a copy and check. I vaguely remember that there were some
changes made to initialization clause. Also, look in the archives for
posts from Andrew Koenig on "value-initialization", you will most likely
find that the initialization was reworked/reworded.

V

I got the active standard, ISO/IEC 14882:2003(E), and still couldn't
find anywhere that stated what you mentioned:

"then the rule is simple: any data member omitted from the initialiser
list, shall be
value-initialised ([class.base.init]/3), not *un*initialised. UDT or
not does not matter. Value-initialisation is performed differently
for
POD than for non-POD, but that does not change the fact that an
omitted
member is value-initialised."

class.base.init/3 stated:

"— if the expression-list of the mem-initializer is omitted, the base
class or member subobject is value initialized (see 8.5);
— otherwise, the subobject indicated by mem-initializer-id is direct-
initialized using expression-list as the initializer (see 8.5)."

This is concerned with not the mem-initializer-id being absent, but
rather the expression-list being absent.

class.base.init/4 states:

"— If the entity is a nonstatic data member of (possibly cv-qualified)
class type (or array thereof) or a base
class, and the entity class is a non-POD class, the entity is default-
initialized (8.5). If the entity is a nonstatic
data member of a const-qualified type, the entity class shall have a
user-declared default constructor.
— Otherwise, the entity is not initialized. If the entity is of const-
qualified type or reference type, or of a
(possibly cv-qualified) POD class type (or array thereof) containing
(directly or indirectly) a member of
a const-qualified type, the program is ill-formed."

So Foo::x will be unitialized if it is left out in the initialisation
list (and not assigned a value in the body of the constructor).

My interpretation of default versus value initialisation is that the
only difference is that default initialisation calls the default
constructor regardless of whether the ctor is user defined or not,
whereas value-initialisation only calls the default constructor if it
was user defined, otherwise it value-intialises the member.

The standard states that:

"The object is default-initialized if there is no initializer, or
value-initialized if the initializer is ()."

So using the example:

class Foo
{
private:
int x;
public:
~Foo() {};

};

If we write:

Foo x; // default-initialisation

Then the (compiler-generated) constructor will be called, which is
equivalent to an empty constructor with an empty initializer list, so
Foo::x will remain uniitialized.

However,

Foo x() // value-initialisation

Then, as there is no user-defined constructor, the members of x will
be value-initialised, which means that Foo::x will be zero
initialised!

Is this correct?

I'm still unsure of what happens if you do not initialise a data
member in the copy constructor, but I think the above needs to be
sorted first.

Taras
Kind of.
Foo x(); is a function declaration :)
Buf Foo x = Foo(); will initialize i to zero and Foo x; won't.

Why did you need a destructor though -- just wondering?

The answer to your second question is also yes. If you have a
user=defined constructor and do not initialize a member, the member will
stay uninitialized.

-Pavel
 
T

Taras_96

So to recap.

* The compiler generated default constructor is a constructor with an
empty initializer list and body
* If a constructor ommits a member from the initialization list:
** If the entity is a nonstatic data member of (possibly cv-qualified)
class type (or array thereof) or a base
class, and the entity class is a non-POD class, the entity is default-
initialized (8.5).
** Otherwise, the entity is not initialized

Foo foo; // default initialization
The default constructor is called (regardless of whether it is user
defined or not)

Foo foo = Foo(); // value initialization
If there IS a user declared constructor, it is called (and program is
ill formed if there is no default constructor)
If 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;

So, 'int Foo::x; will not be initialized IF:

- foo is default initialized and the compiler generated default
constructor is used
- foo is default initialized and a user generated default constructor
is used, *and* Foo::x is ommitted from the initializer list
- foo is value initialized and a user generated default constructor is
used, *and* Foo::x is ommitted from the initializer list

NB: int Foo::x WILL be initialized if foo is value initialized, and
there is no user declared default constructor (unlike if you were to
default initialize foo), as "every non-static data member and base-
class component of T is value-initialized"

This to me is unusual that the behaviour wasn't made consistent... why
not just make it so that all members are always initialized,
regardless of whether the parent object is default initialized, value
initialized, or if a user declared constructor is used.

Taras
 
P

Pavel

Taras_96 said:
So to recap.

* The compiler generated default constructor is a constructor with an
empty initializer list and body
* If a constructor ommits a member from the initialization list:
** If the entity is a nonstatic data member of (possibly cv-qualified)
class type (or array thereof) or a base
class, and the entity class is a non-POD class, the entity is default-
initialized (8.5).
** Otherwise, the entity is not initialized

Foo foo; // default initialization
The default constructor is called (regardless of whether it is user
defined or not)

Foo foo = Foo(); // value initialization
If there IS a user declared constructor, it is called (and program is
ill formed if there is no default constructor)
If 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;

So, 'int Foo::x; will not be initialized IF:

- foo is default initialized and the compiler generated default
constructor is used
- foo is default initialized and a user generated default constructor
is used, *and* Foo::x is ommitted from the initializer list
- foo is value initialized and a user generated default constructor is
used, *and* Foo::x is ommitted from the initializer list

NB: int Foo::x WILL be initialized if foo is value initialized, and
there is no user declared default constructor (unlike if you were to
default initialize foo), as "every non-static data member and base-
class component of T is value-initialized"

This to me is unusual that the behaviour wasn't made consistent... why
not just make it so that all members are always initialized,
regardless of whether the parent object is default initialized, value
initialized, or if a user declared constructor is used.

Taras
My understanding it is for efficiency. If you do not want to initialize
a data member or you cannot initialize it in initializer (which
sometimes happens although not as often as it is used as a justification
for initializing in the body), you would end up to copying to that
member more than once.

As opposed to, for example, Java, C++ does not do static analysis of the
constructor body so it cannot (is not required to) ensure that the data
member that is not initialized in initializer is assigned to (or
initialized by other means) in the body of the constructor. Thus to
allow the calculation of a data member value in the constructor's body
without the overhead of extra copy, C++ also has to allow you to leave
it uninitialized. C++'s cheese (passing function parameters by
references and pointers) is not free -- it stays in the way of complete
static analysis by the compiler.

Certainly there are also situations where you do not want to initialize
at all, either in the initializer or in the body. Imagine you have a
cache of a significant but fixed size, some char cache[MAX_SIZE]. In
order to avoid an extra dynamic memory (de)allocation, you make it a
bare C array. You may have a single boolean "bool cacheDirty"
(initialized to true) or used size "size_t cacheSize;" (initialized to
zero) and avoid unnecessary copying into the whole cache on the creation
of the class.

-Pavel
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top