Topic: Member destruct order in dtor


Author: David R Tribble <dtribble@technologist.com>
Date: 1998/12/22
Raw View
David R Tribble <dtribble@technologist.com> wrote:
> The rationale given for this is that the members should be
> destructed in the exact opposite order that they were constructed,
> but this is a problem because, while there may be multiple ctors,
> each of which construct the members in a different order, there
> is only one dtor (which implicitly destructs the members), and it
> cannot know which ctor was used to contruct the object, and thus
> it cannot know in which order to destruct the members.  The choice
> was thus made (arbitrarily) to construct the members in the order
> they are declared.

Siemel Naran wrote:
> The reason was not "arbitrary".  Being able to construct data
> members in any order would lead to more inefficient programs.
> The compiler now has to keep track of the order the members
> were initialized, and then deinitialize them in that order.
> So there is space penalty for this tracking, as well as time
> penalty.

I understand the reasons; they were not arbitrary.  But the solution
was arbitrary; the order of member declaration was chosen out of
several possibilities.  It was an arbitrary, but completely
reasonable, choice.


David:
> Except that it forces one to arrange the members of a class with
> regard to initialization sequence, rather than with regard to
> structure packing considerations (i.e., memory space optimization).
> This can have a detrimental effect on the size of some classes,
> especially if they contain members of widely mixed sizes.

Siemel:
> No.  We can precede each data element with the "private" keyword
> (although I quite like the "private: private:" idea somewhat).
> Most compilers don't seem to take advantage of this, though.

This does not give the programmer any more control over member
ordering than not specifying 'private:'.  And how does this help
with non-private members?


Siemel:
> Give it a year or two, and optimizers will probably do the
> rearranging optimization.  The order of base class sub-objects
> is unspecified, even in the current standard.  Hence
>     struct Base { char c; }
>     struct Derived : Base { public: int i; public: double d; }
> would normally be arranged to Derived=={c,i,d}
> but could be arranged to Derived=={d,i,c}.

There is no guarantee that a given compiler will ever do this.  (I
seriously doubt we'll see very many compilers do this, ever.)  And
the programmer cannot dictate member ordering, even if he knows
better than the compiler.  Besides, I'm not talking about base
class members, I'm talking about the members within a single class
(derived or otherwise).


David:
> For example:
>     class Foo
>     {
>         Bar     m1;
>         short   m2;
>         char    m3;
>
>     public:
>                 ~Foo(): m3, m2, m1;   // New syntax, specifies
>                                       // member destruct order
>                 Foo();
>                 Foo(int i);
>     };

Siemel:
> Sounds like a nice idea.  One thing I don't like about it is that
> the optimizer should handle the re-arranging, not the human
> programmer.

Unless, of course, the programmer wants to control the layout
ordering or destruction order of the members for reasons that the
compiler cannot possibly know.  This he cannot do today.

My suggestion would give the programmer more control in the cases
where he really does know better than the compiler.  He can always
let the compiler do what it thinks is best in all other cases.

-- David R. Tribble, dtribble@technologist.com --


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/12/22
Raw View
>David:
>> The rationale given for this is that the members should be
>> destructed in the exact opposite order that they were constructed,
>> but this is a problem because, while there may be multiple ctors,
>> each of which construct the members in a different order, there
>> is only one dtor (which implicitly destructs the members), and it
>> cannot know which ctor was used to contruct the object, and thus
>> it cannot know in which order to destruct the members.  The choice
>> was thus made (arbitrarily) to construct the members in the order
>> they are declared.

>Siemel Naran:
>> The reason was not "arbitrary".  Being able to construct data
>> members in any order would lead to more inefficient programs.
>> The compiler now has to keep track of the order the members
>> were initialized, and then deinitialize them in that order.
>> So there is space penalty for this tracking, as well as time
>> penalty.
>
>I understand the reasons; they were not arbitrary.  But the solution
>was arbitrary; the order of member declaration was chosen out of
>several possibilities.  It was an arbitrary, but completely
>reasonable, choice.



Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1998/12/22
Raw View
David R Tribble wrote:

[...]

> Siemel:
> > No.  We can precede each data element with the "private" keyword
> > (although I quite like the "private: private:" idea somewhat).
> > Most compilers don't seem to take advantage of this, though.
>
> This does not give the programmer any more control over member
> ordering than not specifying 'private:'.  And how does this help
> with non-private members?

The compiler is allowed to re-order elements in memory, if
there's an access specifier in between. For public members,
this access specifier will be "public:", of course.

Example:

class X
{
public:
  X(); // must be non-POD!
private:
  char c;
  int i;
  char d;
};

must be layed out like c___iiiid___ for 4-byte-aligned int with
sizeof(int)==4. However:

class Y
{
public:  Y();
private: char c;
private: int i;
private: char d;
};

may be layed out as cd__iiii in this case.
The order of initialisation is the same in both cases, of course.

[...]


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1998/12/22
Raw View
David R Tribble wrote in message <367EECF1.6FEBD846@technologist.com>...

>My suggestion would give the programmer more control in the cases
>where he really does know better than the compiler.  He can always
>let the compiler do what it thinks is best in all other cases.


It is possible (but ugly) to do this now.  I don't think the ugliness is too
important, since IMHO the need for this kind of control is rare.

If you need to be this careful with layout you probably already know your
types' alignment requirements.

Suppose I want class FooBar to contain a Foo and a Bar (Foo first), but I
need the Bar to be constructed first.  Overall alignment is same as double,
and no padding is necessary between the Foo and the Bar.

Disclaimer: I haven't run this through a compiler or tested it.

class FooBar
{
  union
  {
    unsigned char vec[sizeof(Foo)+sizeof(Bar)];
    double d;
  };
  Foo& f(){ return reinterpret_cast<Foo&>(vec); }
  Bar& b(){ return reinterpret_cast<Bar&>(vec[sizeof(Foo)]); }

public:
  FooBar() // Use placement new to initialize members
  {
    new(&b()) Bar;
    try { new(&f()) Foo; } catch(...){ b().~Bar(); throw; }
  }

  // Assume destructors don't throw
  ~FooBar(){ f().~Foo(); b().~Bar(); }
};

Let me amend my previous statement.  Too ugly for words.





[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 1998/12/22
Raw View
In article <slrn77ucf3.vtr.sbnaran@localhost.localdomain>, Siemel Naran
<sbnaran@localhost.localdomain.COM> writes
>By preceding each variable with a redundant access specifier, we
>give the compiler full license in rearranging the struct.  I now
>see one problem with this:  There are just too many possible
>layouts.  For n data members, there are factorial(n) possible
>layouts, and it's going to be tough for the compiler to figure
>out which one gives the smallest sizeof.  It can be done exactly
>for small n, and approximately for large n.

As the compiler 'knows' the alignment requirements it does not need to
test all possible cases.  I think an algorithm along these lines works:
categorise all elements by alignment constraints.  Alocate locations for
all those with the highest constraint.  Check to see if the resulting
padding can be used by the next strictist set, if so place as many as
possible in the padding and then place the remainder afterwards.  Repeat
the process until all elements have been placed.



Francis Glassborow      Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: David R Tribble <dtribble@technologist.com>
Date: 1998/12/18
Raw View
Currently, constructors cannot affect the order in which members
are initialized/constructed within a class; the order depends on
the sequence of the member declarations within the class declaration
and not the order of member-initializers within the (one or more)
constructor functions.

The rationale given for this is that the members should be
destructed in the exact opposite order that they were constructed,
but this is a problem because, while there may be multiple ctors,
each of which construct the members in a different order, there
is only one dtor (which implicitly destructs the members), and it
cannot know which ctor was used to contruct the object, and thus
it cannot know in which order to destruct the members.  The choice
was thus made (arbitrarily) to construct the members in the order
they are declared.

All of which seems like a reasonable compromise.

Except that it forces one to arrange the members of a class with
regard to initialization sequence, rather than with regard to
structure packing considerations (i.e., memory space optimization).
This can have a detrimental effect on the size of some classes,
especially if they contain members of widely mixed sizes.

How about allowing the dtor to specify the destruction sequence
for the members?  That way, the programmer is free to arrange
the members for the best space utilization, while not breaking
the guarantee of a fixed and predictable member ctor/dtor
ordering.

For example:
    class Foo
    {
        Bar     m1;
        short   m2;
        char    m3;

    public:
                ~Foo(): m3, m2, m1;   // New syntax, specifies
                                      // member destruct order
                Foo();
                Foo(int i);
    };

    Foo::~Foo()   // Members destructed in order m3, m2, m1
    { ... }

    Foo::Foo():
        m2(0), m3(0), m1(0)   // Order is really m1, m2, m3
    { ... }

    Foo::Foo(int i):
        m3(i), m1(i), m2(i)   // Order is really m1, m2, m3
    { ... }

The declaration for the class dtor (optionally) specifies the
order that the class members are to be destructed; the order
that they are constructed is simply the reverse order.  If no
members are listed, the construction/destruction order reverts
to being based on the declaration order within the class
declaration.

(I originally thought that the dtor function *definition* could
specify the member destruction sequence, but this puts too large
a burden on the compiler, and would probably require that the
dtor definition occur prior to all ctor definition(s).  Placing
the destruct sequence in the class declaration solves this
problem.)

Comments?

-- David R. Tribble, dtribble@technologist.com --


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: William Roeder <roeder@dba-sys.com>
Date: 1998/12/19
Raw View
David R Tribble wrote:
>
> Currently, constructors cannot affect the order in which members
> are initialized/constructed within a class; the order depends on
> the sequence of the member declarations within the class declaration
> and not the order of member-initializers within the (one or more)
> constructor functions.
>
> The rationale given for this is that the members should be
> destructed in the exact opposite order that they were constructed,
> but this is a problem because, while there may be multiple ctors,
> each of which construct the members in a different order, there

Each member is constructed in the same order.  To use your example:
         m2(0), m3(0), m1(0)   // Order is really m1, m2, m3
         m3(i), m1(i), m2(i)   // Order is really m1, m2, m3

> is only one dtor (which implicitly destructs the members), and it
> cannot know which ctor was used to contruct the object, and thus
> it cannot know in which order to destruct the members.  The choice
> was thus made (arbitrarily) to construct the members in the order
> they are declared.

Which is the same as the order that they are constructed.

>
> All of which seems like a reasonable compromise.
>
> Except that it forces one to arrange the members of a class with
> regard to initialization sequence, rather than with regard to
> structure packing considerations (i.e., memory space optimization).
> This can have a detrimental effect on the size of some classes,
> especially if they contain members of widely mixed sizes.

You can always use:
foo() { m3=x; m1=y; m2=z;}
which initializes the members in the order specified.
There is a difference between construction and initialization.  Usually
we think of them as one.
If your thinking about initialization sequences then you should not be
using an initialization list because a simple member reordering will
break it.  But sequence dependent initialization in the contructor body
and document the required ordering in the comments their.

> How about allowing the dtor to specify the destruction sequence
> for the members?  That way, the programmer is free to arrange
> the members for the best space utilization, while not breaking
> the guarantee of a fixed and predictable member ctor/dtor
> ordering.

The order that they are destructed, does not matter except for
inheritance.  The releasing of resources in the destructor may be
sequence dependent also.  That is the purpose of the destructor body.
--
Bill Roeder
My opinions are my own and do not reflect those of my employer.
Why do people put the above line in their signature?
If they reflected my employer then he would have sent the message!



[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: David R Tribble <dtribble@technologist.com>
Date: 1998/12/19
Raw View
Before I get any complaints about my example code, let me post
a slightly modified version, with a different construct/destruct
member ordering, which will illustrate my point better than my
original example:

    class Foo   // Version 2
    {
        Bar     m1;
        short   m2;
        char    m3;

    public:
                ~Foo(): m1, m3, m2;   // New syntax, specifies
                                      // member destruct order
                Foo();
                Foo(int i);
    };

    Foo::~Foo()   // Members destructed in order m1, m3, m2
    { ... }

    Foo::Foo():
        m1(0), m2(0), m3(0)   // Order is really m2, m3, m1
    { ... }

    Foo::Foo(int i):
        m3(i), m1(i), m2(i)   // Order is really m2, m3, m1
    { ... }

Destructing member m1 last might be useful if, for example, it
contains a reference to member m2 or m3.  At the same time,
though, we'd like it to occur first in our class in the hopes
that class Foo will have a smaller memory footprint.

-- David R. Tribble, dtribble@technologist.com --


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/12/19
Raw View
On 18 Dec 1998 15:54:11 GMT, David R Tribble <dtribble@technologist.com> wrote:

>The rationale given for this is that the members should be
>destructed in the exact opposite order that they were constructed,
>but this is a problem because, while there may be multiple ctors,
>each of which construct the members in a different order, there
>is only one dtor (which implicitly destructs the members), and it
>cannot know which ctor was used to contruct the object, and thus
>it cannot know in which order to destruct the members.  The choice
>was thus made (arbitrarily) to construct the members in the order
>they are declared.

The reason was not "arbitrary".  Being able to construct data
members in any order would lead to more inefficient programs.
The compiler now has to keep track of the order the members
were initialized, and then deinitialize them in that order.
So there is space penalty for this tracking, as well as time
penalty.


>Except that it forces one to arrange the members of a class with
>regard to initialization sequence, rather than with regard to
>structure packing considerations (i.e., memory space optimization).
>This can have a detrimental effect on the size of some classes,
>especially if they contain members of widely mixed sizes.

No.  We can precede each data element with the "private" keyword
(although I quite like the "private: private:" idea somewhat).
Most compilers don't seem to take advantage of this, though.
Give it a year or two, and optimizers will probably do the
rearranging optimization.  The order of base class sub-objects
is unspecified, even in the current standard.  Hence

struct Base { char c; }
struct Derived : Base { public: int i; public: double d; }

would normally be arranged to Derived=={c,i,d}
but could be arranged to Derived=={d,i,c}.


>For example:
>    class Foo
>    {
>        Bar     m1;
>        short   m2;
>        char    m3;
>
>    public:
>                ~Foo(): m3, m2, m1;   // New syntax, specifies
>                                      // member destruct order
>                Foo();
>                Foo(int i);
>    };

Sounds like a nice idea.  One thing I don't like about it is that
the optimizer should handle the re-arranging, not the human
programmer.


--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]