Topic: Full-Object Initializers (was Why's of C++ -- (Constructors))


Author: Lawrence.Crowl@eng.sun.com (Lawrence Crowl)
Date: 1999/09/09
Raw View
In article <memo.19990905202453.58323D@btinternet.com>,
Dave Harris <brangdon@cix.co.uk> wrote:
>Lawrence.Crowl@eng.sun.com (Lawrence Crowl) wrote:
>> I propose here a reasonable "minimal extension" semantics for such
>> a construct.
>> [...]
>> Any problems?
>
>Looks good to me. Just to confirm the intent for this case:
>
>    Foo::Foo( int a ) : bar1(0), bar2(a), bar3(0)
>    {}
>
>    Foo::Foo( int a, bool b )
>    try
>    : Foo( a )
>    {
>        // Normal case; fully constructed here.
>    }
>    catch( ... )
>    {
>        // Fully destructed here.
>    }
>    // Full constructed on exit.
>
>If Foo has 3 sub-objects and Foo(int) throws, all are guaranteed
>destructed before the catch in Foo(int,int) starts running, even if the
>exception happens in the middle one. Only Foo(int) can reasonably know
>what's to be done in that case.

That is my intent.

>
>Then the Foo(int,int)'s catch starts running with the object in a known
>state (ie fully unconstructed) and can do whatever is required to recover.
>If there are no more throws, the object is constructed on exit.

It doesn't work that way currently, and I don't think it should under
what I propropose.  Currently, the try body catch block cannot
"recover".  All it can do is explicitly rethrow or implicitly rethrow.

These semantics are present because there is no syntactic way to
re-execute an initializer.

  Lawrence Crowl                650-786-6146   Sun Microsystems, Inc.
                  Lawrence.Crowl@Eng.Sun.Com   901 San Antonio Road, UMPK16-303
                                               Palo Alto, California, 94303

[ 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: (Lawrence Crowl) <Lawrence.Crowl@Eng.Sun.COM>
Date: 1999/09/10
Raw View
Jerry Leichter <jerrold.leichter@smarts.com> writes:
 >| (1) Upon return from a constructor, the object is constructed.
 >
 >I don't think this is necessary, or even particularly desireable.

Why do you think so?

 >
 >If the, after [called initializer] Foo(int,false) completes, the
 >object is constructed, just what is [constructor] Foo(int)'s body
 >*doing*?

There are two answers.  First, the body is doing nothing.  Constructor
Foo(int)'s sole purpose is to fill in the additional parameter false.
Second, the body is setting additional initial state.  For instance, a
constructor string(const char*) is technically unnecessary, because you
could have string() and operator=(const char*), but string(const char*)
is desireable because it is likely both more concise and more efficient.

 >Whatever it is, it isn't the kind of thing constructors *do*, since
 >it's operating on an already-constructed object - and according to C++
 >semantics, it's impossible to construct an already-constructed
 >object.

Your arguments are getting circular here.  C++ isn't a semantically
tight language, so constructors are what we use them for.  I intend to
extend their use just a little bit.

 >
 >Today, in C++, the way to get the same effect is to define a function
 >callable from both constructors to do the common part of the work.
 >(This isn't always possible, since a function can't to subobject
 >initialization, only assignment to subobjects.)

And therein lies the problem.  I want to encapsulate the subobject
initialization.

 >From a logical point of view, why should there be a difference
 >between calling the constructor and calling such a
 >pseudo-constructor?

Because C++, in its syntax, forces a distinction.

 >They do exactly the same thing.  However, your constraint seems to
 >say that one of them leaves the object constructed, and the other
 >doesn't.

The two approaches don't to the same thing, as your statement above
shows.  My constraint says the same thing about both constructors -- on
successfull completion of a constructor, its object is fully
constructed.  Note that I did not include the converse constraint -- an
object is fully constructed only when completing a constructor.  This
constraint would of course result in an inconsistency when there are
two constructors.

 >
 >| (2) Upon throw from a constructor, the object is not constructed.
 >|     Any sub-objects that have been constructed are destructed.
 >
 >Since both Foo's are constructors, satisfying this constraint requires
 >that, if Foo(int) throws, the object magically goes from constructed to
 >non-constructed, without being destructed.  No such transition is
 >possible in C++.

It doesn't magically go, the full destructor is called, at the point in
other constructors where the sub-object destructors would have been
called.

 >
 >| (3) The implemenation of constructors must be consistent
 >|     whether or not the implementation of other constructors is know.
 >
 >Why?

Because with separate compilation, the compiler will not know what the
implementation of other constructors is.  The common scenario is an
inline "caller" constructor and an out-of-line and externally defined
"callee" constructor.

 >
 >Really, part of the problem must here is that, for constructors, it
 >looks as if you need to forward declare a "richer" signature than for
 >normal functions.  Suppose we defined a "full declaration" of a
 >constructor to look like the following:
 >
 > Foo::Foo(<arglist>) : <subclass-and-member-list>;
 >
 >where <subclass-and-member-list> as a list of *just the names* of
 >members and subclasses that this constructor will initialize.  The
 >implementation may only provide initializers for things on the list, or
 >another initializer for this same class.  Any member or subclass not on
 >the list must be default-initializable, and is treated as if an explicit
 >default initialization was requested.  Then we say that one initializer
 >may include a call to another initializer only if a full declaration for
 >that other initializer has appeared.

I don't follow.  My proposal doesn't require any such signature, nor
any default-initializable objects.

 >
 >Since the entire class declaration must already have appeared by the
 >time we are compiling any initializer for the class, the compiler knows
 >what all the initializers are, whether any other initializer calls them,
 >and, when they call other initializers, the full declarations for those
 >other initializers.  This is all the information needed to generate just
 >the code required.

This is only if you require initializers as part of the declaration.
I don't think this is desirable because it exposes more of the
implementation of constructors in the class interface, which constrains
programmers in what they can change without causing a recompile of all
their clients.  (This is a big deal if you're delivering libraries or
operating systems, rather than working on end applications.)

  Lawrence Crowl                650-786-6146   Sun Microsystems, Inc.
                  Lawrence.Crowl@Eng.Sun.Com   901 San Antonio Road, UMPK16-303
              http://www.cs.orst.edu/~crowl/   Palo Alto, California, 94303
---
[ 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: Jerrold Leichter <jerrold.leichter@smarts.com>
Date: 1999/09/10
Raw View
[I see our messages are "crossing in the mail"]

|  >| (1) Upon return from a constructor, the object is constructed.
|  >
|  >I don't think this is necessary, or even particularly desireable.
|
| Why do you think so?
|
|  >
|  >If the, after [called initializer] Foo(int,false) completes, the
|  >object is constructed, just what is [constructor] Foo(int)'s body
|  >*doing*?
|
| There are two answers.  First, the body is doing nothing.  Constructor
| Foo(int)'s sole purpose is to fill in the additional parameter false.
| Second, the body is setting additional initial state.  For instance, a
| constructor string(const char*) is technically unnecessary, because you
| could have string() and operator=(const char*), but string(const char*)
| is desireable because it is likely both more concise and more efficient.

For a constructor that doesn't call a full-object constructor - that is, any
constructor in C++ today - the object is *not* constructed until after the
body has returned.  So in a constructor today, the body is ... constructing
the object.  Under your semantics, however, the body of a constructor that
*does* invoke a full-object constructor apparently is operating on an
already-constructed object.  So I guess despite its name, it isn't
really a constructor after all....  Just what percentage of phrases in the
standard that refer to "a constructor" will you have to change to "a
constructor that does not invoke a full-object constructor" in order to slide
your semantics into the language?

|  >Whatever it is, it isn't the kind of thing constructors *do*, since
|  >it's operating on an already-constructed object - and according to C++
|  >semantics, it's impossible to construct an already-constructed
|  >object.
|
| Your arguments are getting circular here.  C++ isn't a semantically
| tight language, so constructors are what we use them for.  I intend to
| extend their use just a little bit.

Wow.  C++ "isn't a semantically tight language", so why should we bother to
try to improve things?  No, let's just keep tossing new things in and hope for
the best.

|  >Today, in C++, the way to get the same effect is to define a function
|  >callable from both constructors to do the common part of the work.
|  >(This isn't always possible, since a function can't to subobject
|  >initialization, only assignment to subobjects.)
|
| And therein lies the problem.  I want to encapsulate the subobject
| initialization.
|
|  >From a logical point of view, why should there be a difference
|  >between calling the constructor and calling such a
|  >pseudo-constructor?
|
| Because C++, in its syntax, forces a distinction.

Yes, but you then make the distinction much more complex:  There are
"normal" constructors, and constructors invoking full-object constructors.
Oh, and BTW, there is no way to tell which is which without seeing the
*implementation*:  The name and even the full declaration don't tell you.

|  >| (2) Upon throw from a constructor, the object is not constructed.
|  >|     Any sub-objects that have been constructed are destructed.
|  >
|  >Since both Foo's are constructors, satisfying this constraint requires
|  >that, if Foo(int) throws, the object magically goes from constructed to
|  >non-constructed, without being destructed.  No such transition is
|  >possible in C++.
|
| It doesn't magically go, the full destructor is called, at the point in
| other constructors where the sub-object destructors would have been
| called.

No, in normal constructors the destructor would *not* be called, because the
object isn't constructed until the constructor returns.

|  >
|  >Really, part of the problem must here is that, for constructors, it
|  >looks as if you need to forward declare a "richer" signature than for
|  >normal functions.  Suppose we defined a "full declaration" of a
|  >constructor to look like the following:
|  >
|  > Foo::Foo(<arglist>) : <subclass-and-member-list>;
|  >
|  >where <subclass-and-member-list> as a list of *just the names* of
|  >members and subclasses that this constructor will initialize.  The
|  >implementation may only provide initializers for things on the list, or
|  >another initializer for this same class.  Any member or subclass not on
|  >the list must be default-initializable, and is treated as if an explicit
|  >default initialization was requested.  Then we say that one initializer
|  >may include a call to another initializer only if a full declaration for
|  >that other initializer has appeared.
|
| I don't follow.  My proposal doesn't require any such signature, nor
| any default-initializable objects.

Your proposal doesn't require any such signature, and as a result it (a) is a
very limited extension, only usable in limited circumstances; (b) even so, it
makes a hash out of existing semantics.  You want more power, you need to
provide the compiler with more information.

The default initialization stuff is a red herring.  If you like, require that,
if the full declaration for a constructor lists a subclass or member name,
then the constructor *must* initialize that subclass or member.

|  >
|  >Since the entire class declaration must already have appeared by the
|  >time we are compiling any initializer for the class, the compiler knows
|  >what all the initializers are, whether any other initializer calls them,
|  >and, when they call other initializers, the full declarations for those
|  >other initializers.  This is all the information needed to generate just
|  >the code required.
|
| This is only if you require initializers as part of the declaration.
| I don't think this is desirable because it exposes more of the
| implementation of constructors in the class interface, which constrains
| programmers in what they can change without causing a recompile of all
| their clients.  (This is a big deal if you're delivering libraries or
| operating systems, rather than working on end applications.)

No; my suggestion was that only the *names* of the subclasses and members
that would be initialized need be listed.  The expressions used to initialize
them would not be part of the interface.  If you follow the convention that,
when you invoke a full-object initializer, you never initialize anything else,
then you can simply list *all* subclasses and members in the list (perhaps
there can be a special notation for this), and you get exactly the effect you
are proposing:  No change to the full-object initializers will force you to
change the initializers that use them, and the compiler will actually complain
if your convention (no other initializers) is violated.  (It was to allow this
kind of thing that I added the stuff about "default initialization".)

       -- Jerry
---
[ 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: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/09/10
Raw View
Lon Willett wrote:

I think it's easier to see what's going on, if you rewrite
the code to currently legal one:

>
> To illustrate point #3 made above, consider the following, where the
> "member contract" (i.e. your version) is used:
>
>     class Bar
>     {
>     public:
>         Bar(args1) : init_list { do_stuff(); }
>         Bar(args2) : Bar(args1) { do_more_stuff(); }
>         ~Bar() { do_destructor_stuff(); }
>     }

This results in:

class BarBase
{
public:
  BarBase(args1): init_list { do_stuff(); }
  ~BarBase() { do_destructor_stuff(); }
};

class Bar: BarBase
{
public:
  Bar(args1): BarBase(args1) {}
  Bar(args2): BarBase(args1) { do_more_stuff(); }
};

That is, the destructor is supposed to undo the args1 stuff,
while the args2 extra stuff doesn't add anything more to undo
(although it may change the state generated by the args1
constructor).

>
> This can be converted to use the "constructor contract" (i.e. my
> version) as follows:
>
>     class Bar
>     {
>     public:
>         Bar(args1) : init_list { do_stuff(); }
>         Bar(args2) : Bar(args1)
>             {
>                 try {
>                     do_more_stuff();
>                 }
>                 catch(...) {
>                     destroy();
>                     throw;
>                 }
>             }
>         ~Bar() { destroy(); }
>     private:
>         void destroy() { do_destructor_stuff(); }
>     }

This would result in

class BarBase
{
public:
  BarBase(args1): init_list { do_stuff(); }
};

class Bar: BarBase
{
public:
  Bar(args1): BarBase(args1) {}
  Bar(args2): BarBase(args1)
  { try { do_more_stuff(); } catch(...) { destroy(); throw; } }
  ~Bar() { destroy(); }
private:
  void destroy() { do_destructor_stuff(); }
};

Here, destroy() is responsible for cleaning up even the
BarBase stuff.

[...]

> On the other hand, consider the following example, which illustrates
> what I consider the primary use of proposed extension.
>
> Start with the following partial class definition (in strictly
> conventional pseudo-C++):
>
>     class Foo
>     {
>     public:
>         Foo(args, more_args)
>             : init_list
>             {
>                 do_some_stuff();
>                 try {
>                     do_some_more_stuff(more_args);
>                 }
>                 catch(...)
>                 {
>                     undo_some_stuff();
>                     throw;
>                 }
>                 assert(some_condition());
>             }
>
>         Foo(other_args, yet_more_args)
>             : init_list
>             {
>                 do_some_stuff();
>                 try {
>                     do_yet_more_stuff(yet_more_args);
>                 }
>                 catch(...) {
>                     undo_some_stuff();
>                     throw;
>                 }
>                 assert(!some_condition());
>             }
>
>         ~Foo()
>             {
>                 if (some_condition()) {
>                     undo_some_more_stuff();
>                 } else {
>                     undo_yet_more_stuff();
>                 }
>                 undo_some_stuff();
>             }
>     };
>
> Assuming the stuff in "init_list" is sufficiently complicated and/or
> tricky to justify a desire to avoid its (assumed) duplication, then
> this could be rewritten with "full-body initialisers" using the
> "constructor contract" as follows:
>
>     class Foo
>     {
>     private:
>         // This "ctor" only partially initialises the object
>         Foo(init_args)
>             : init_list
>             {
>                 do_some_stuff();
>             }
>     public:
>         Foo(args, more_args)
>             : Foo(init_args)
>             {
>                 try {
>                     do_some_more_stuff(more_args);
>                 }
>                 catch (...)
>                 {
>                     undo_some_stuff();
>                     throw;
>                 }
>                 assert(some_condition());
>             }
>
>         Foo(other_args, yet_more_args)
>             : Foo(init_args)
>             {
>                 try {
>                     do_yet_more_stuff(yet_more_args);
>                 }
>                 catch (...)
>                 {
>                     undo_some_stuff();
>                     throw;
>                 }
>                 assert(!some_condition());
>             }
>
>         ~Foo()
>             {
>                 if (some_condition()) {
>                     undo_some_more_stuff();
>                 } else {
>                     undo_yet_more_stuff();
>                 }
>                 undo_some_stuff();
>             }
>     };
>
> Note that this is just a straight-forward factoring of the "init_list"
> and "do_some_stuff()" code from the two constructors into a newly
> created, private "partial-constructor".  Very simple.  Very easy.

This would basically be the same as

class FooBase
{
public:
  FooBase(init_args): init_list { do_some_stuff(); }
};

class Foo: FooBase
{
public:
  Foo(args, more_args): FooBase(init_args)
  {
    try { do_some_more_stuff(more_args); }
    catch(...) { undo_some_stuff(); throw; }
    assert(some_condition());
  }
  Foo(other_args, yet_more_args): FooBase(init_args)
  {
    try { do_yet_more_stuff(yet_more_args); }
    catch(...) { undo_some_stuff(); throw; }
    assert(!some_condition());
  }
  ~Foo()
  {
    if (some_condition()) { undo_some_more_stuff(); }
    else { undo_yet_more_stuff(); }
    undo_some_stuff();
  }
};

One thing you see here, which could also be seen in the
original code, is that undo_some_stuff() is called _three_
times: Once per constructor in case an exception was thrown,
and once in the destructor.
However, in this version, there's also seen the natural place
where undo_some_stuff belongs to: In the destructor belonging
to the constructor which has do_some_stuff. In the rewritten
version, it's clear that this is the destructor of FooBase;
however in the full-object initializer case, there is no such
constructor.

The rewritten code would look like this with conventional
base class code:

class FooBase
{
public:
  FooBase(init_args): init_list { do_some_stuff(); }
  ~FooBase() { undo_some_stuff(); }
};

class Foo: FooBase
{
  Foo(args, other_args): FooBase(init_args)
  { do_some_more_stuff(); assert(some_condition()); }

  Foo(other_args, yet_more_args): FooBase(init_args)
  { do_yet_more_stuff(); assert(!some_condition()); }

  ~Foo()
  {
    if (some_condition()) undo_yet_more_stuff();
    else undo_some_more_stuff();
  }
};

Now you see where the confusion really comes from: There is
some stuff which should be done after the helper constructor
finished, and some stuff that should be done only after the
final constructor finished. However, with the full-object
initializer idiom, there's only _one_ destructor, and the
question is just _which_ of the two destructors in the
case above should it represent.

The "member constract" version says the destructor
is the base class destructor. The "constructor constract"
version says the destructor is the derived class
destructor. Both versions suck, since for a clean design you
need _both_ destructors. Otherwise you'll end up emulating
one of the destructors with try/catch.

Note that with the full-body initializer, things could go
even worse, since the other constructor called could
actually have a full-object initializer itself.

IMHO the base class solution is far cleaner than any of the
full-body initializer solutions. So if the szenaro given is
the expected typical scenario, then full-object initializers
are really not worth it, since they only tend to make the
design worse.

Moreover, the base class version allows more flexibility,
since you can even do this:

class FooBase
{
public:
  FooBase(args): initializers { do_something(); }
  ~FooBase() { undo_something(); }
  member_variables;
};

class Foo: FooBase
{
public:
  FooBase(args, more_args): FooBase(args), more_initializers
  { do_more(); }

  FooBase(args, other_args): FooBase(args), other_initializers
  { do_other(); }

  ~FooBase() { ... }
private:
  more_member_variables;
};

>
> And, assuming that the "do_some_more_stuff()"/"do_yet_more_stuff()"
> code must be executed before the class invariants hold (which was
> meant to be implied), this code is broken for the "member contract"
> version of initialiser lists.

The point is that there are _two_ contracts: The one which is
guaranteed by the private constructor (on which the public
constructors and undo_some_thing() rely), and the full class
invariant (on which the class destructor relies).

>  As a matter of fact, the easiest way
> that I can see of accomplishing this (in general) with the "member
> contract" is to shove all the code into the private ctor; i.e. to
> convert it to one of the cases where the "member contract" is
> equivalent to the "constructor contract".  This can be considerably
> more complicated and less readable:

[...]

> Forcing *all* the code, and arguments, into the private constructor
> seems to work against the goal of simplifying the structure.  Thus I
> think the "constructor contract" is the better choice.

I think the Base class solution is the best choice. It clearly
separates the two invariants involved. It removes the necessity
of repeating the same

  try
  {
    something()
  }
  catch(...)
  {
    undo_full_object_initializer_constructor_stuff();
    throw;
  }

code (and even if we factor the catch(...) into it's own
function, we still must remember to call that - error prone).

And now there comes the great thing about it: It works just today!

>
> In truth, I think the cases where the two versions of this proposal
> can be distinguished are rare, especially if one has a good design.
> So this discussion is about unusual cases of what would be a
> not-very-often used feature that we might see in future versions of
> C++.  But I certainly do recall cases where I would have liked to have
> this feature (either version).

IMHO it would be a hell for exception safety.

Yes I've first thought it to be a good idea as well.
Until I saw the base class solution posted here, seeing that
it would not be necessary.
And now the destructor discussion with exception problems
totally convinced me that it would be a very bad idea.


[ 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: "Anders J. Munch" <andersjm@dancontrol.dk>
Date: 1999/09/11
Raw View
Lon Willett wrote in message <7r8mb3$j7q$1@nnrp1.deja.com>...
[long post omitted]

Lon, I am very confused by your terminology.  Are you using "member
contract" for the case where the object is considered constructed after the
full-object initializer call, and "constructor contract" for the case where
it isn't?  That naming sounds counterintuitive to me.

More reasons to favor not-constructed semantics:
- Factoring out common code is semantically neutral, making it possible to
restructure code, use multi-level constructors-calling-constructors etc.
without thinking too much about it.
- The alternative adds overhead for the try-catch-destroy-rethrow stuff.

Anders.
---
[ 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: Jerry Leichter <jerrold.leichter@smarts.com>
Date: 1999/09/09
Raw View
| Several posters have proposed varying semantics for what I will call
| full-object initializers.  Full-object initializers are the
| specification of an initializer with the same type as the
| constructor's object type....  I propose here a reasonable "minimal
| extension" semantics for such a construct.
|
| Syntax Example
|
|     Foo::Foo( int a, bool b )
|     : sub_object_initializers
|     { }
|
|     Foo::Foo( int a )
|     : Foo( a, false )
|     { }
| ...
| Semantic Constraints
|
| (1) Upon return from a constructor, the object is constructed.

The point of this constraint seems to be that after Foo(int,false) has
been executed, the object is constructed, even though Foo(int)
hasn't executed its body yet.  I don't think this is necessary, or even
particularly desireable.

In C++ semantics as it exists today, "constructed" is a boolean state.
A constructor can only be applied to a non-constructed object -
yielding, if it succeeds, a constructed object; a destructor can only be
applied to a constructed object, yielding a non-constructed object.
(It's possible for a non-constructed object to have constructed compo-
nents, whether subclasses or embedded objects, but that's a separate
issue.)

If the, after Foo(int,false) completes, the object is constructed, just
what is Foo(int)'s body *doing*?  Whatever it is, it isn't the kind of
thing constructors *do*, since it's operating on an already-constructed
object - and according to C++ semantics, it's impossible to construct an
already-constructed object.

Today, in C++, the way to get the same effect is to define a function
callable from both constructors to do the common part of the work.
(This isn't always possible, since a function can't to subobject
initialization, only assignment to subobjects.)  From a logical point of
view, why should there be a difference between calling the constructor
and calling such a pseudo-constructor?  They do exactly the same thing.
However, your constraint seems to say that one of them leaves the object
constructed, and the other doesn't.

| (2) Upon throw from a constructor, the object is not constructed.
|     Any sub-objects that have been constructed are destructed.

Since both Foo's are constructors, satisfying this constraint requires
that, if Foo(int) throws, the object magically goes from constructed to
non-constructed, without being destructed.  No such transition is
possible in C++.

| (3) The implemenation of constructors must be consistent
|     whether or not the implementation of other constructors is know.

Why?

Really, part of the problem must here is that, for constructors, it
looks as if you need to forward declare a "richer" signature than for
normal functions.  Suppose we defined a "full declaration" of a
constructor to look like the following:

 Foo::Foo(<arglist>) : <subclass-and-member-list>;

where <subclass-and-member-list> as a list of *just the names* of
members and subclasses that this constructor will initialize.  The
implementation may only provide initializers for things on the list, or
another initializer for this same class.  Any member or subclass not on
the list must be default-initializable, and is treated as if an explicit
default initialization was requested.  Then we say that one initializer
may include a call to another initializer only if a full declaration for
that other initializer has appeared.

Since the entire class declaration must already have appeared by the
time we are compiling any initializer for the class, the compiler knows
what all the initializers are, whether any other initializer calls them,
and, when they call other initializers, the full declarations for those
other initializers.  This is all the information needed to generate just
the code required.
       -- Jerry


[ 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: Lon Willett <lwillett@my-deja.com>
Date: 1999/09/09
Raw View
In article <37D56F04.5575@wanadoo.fr>,
  Valentin Bonnard <Bonnard.V@wanadoo.fr> wrote:
>
> Lon Willett wrote:
>
> > But the main disadvantage that I see to definition #3 is strictly
> > conceptual.  I don't want my destructor to be called automatically
> > until my constructor (whichever one was invoked) has successfully
> > completed. This is how things work now, and it makes sense to me.
>
> Short answer: if you are right, then you are wrong

I don't think so.  I've found that the times that I'm right are usually
those when I'm right.  :-)

> Long answer:
>
> But the main disadvantage that I see to your definition
> is strictly conceptual.
>
> I do want dtor to be called for an object x if an exception
> is thrown and a ctor of x has successfully completed.
>
> This is how things work now, and it makes sense to me.

Short question: Why?

Long question: Private member functions very often do *not* preserve
class invariants.  They are only used internally by the public members
which *do* preserve the class invariants.  And if private members are
free to break the member function "contract", why not allow private
ctors that don't keep the "constructor" contract, i.e. that don't
fully construct the object to the point where the class invariants
hold?  (Currently, there is not much use for such a beast, but if C++
adopts the proposed mechanism, then there would be: namely factoring
out the common code in the initialisation list of ctors, without
necessarily finishing the construction).

I'll elaborate, since I don't think that this is really clear.

A constructor currently has the following "contract":

    On entry to the body, the sub-objects have been constructed as
    specified by the initilisation list, but the class invariants do
    not necessarily hold.  On normal termination, the class invariants
    must hold.  On exceptional termination (i.e. exiting with throw),
    sub-objects are automatically destroyed, but any other cleanup
    required must be done explicitly (using try/catch as needed).

    Exception: it is conceivable that one could have a ctor that does
    *not* properly construct the object, as long as its usage is
    restricted in such a way (preferably by making it private) that
    the object will be fully constructed "immediately after" this
    "constructor" is called (meaning the object somehow gets fixed
    before it gets used).  I don't recall ever using something like
    this, but it is conceivable.

Member functions have the following "contract":

    The class invariants hold on entry.  On exit, they must also hold.
    try/catch may need to be used to assure this in the presence of
    exceptions.

    Exception: members whose usage is somehow restricted (generally by
    making them "private") are not bound by this contract, and can be
    used in just about any way desired.  Indeed, code for use by
    ctors/dtors is often put into private members, and clearly works
    with different requirements.

And, of course, the destructor has the following "contract":

    On entry the class invariants hold.  Any necessary cleanup should
    be done.  On normal exit, the sub-objects are automatically
    destroyed.  Exceptions should not be thrown (if at all possible).

The issue here is which contract should be applicable for a
constructor which invokes another constructor (a "full-body
initialiser", as someone called it).  I prefer the constructor
contract; you (and others) prefer the member contract (which thus
implies that exceptions must invoke the destructor).  Either is
consistant; the issue is not right vs. wrong, but rather is one of
aesthetics and pragmatism.

I advocate the constructor contract because: (1) it seems like the
natural contract for a constructor to use, (2) it makes it easier to
take conventional constructors and factor out the initialiser list
code, which is the main point of this proposal (the initialiser list
is the only significant piece of code which can't currently be
factored out into a private member function), and (3) with my
semantics, it is easy to duplicate the functionality you are
proposing, but the converse is not true (see example below).

The objection to using the constructor contract seems to be that it
implies that the constructor invoked in the initialiser list does not
necessarily fully construct the object.  While such a constructor does
not deserve the name "constructor" (perhaps "initialiser"? or "partial
constructor"?), I don't see this as a major problem.  Just make the
"partial constructors" private, as one does with member functions that
don't fulfill the standard member-function contract.  And put a
comment with it explaining what is going on.

Some examples are in order.  However, note that the two possibilities
can only be distinguished when both (1) the destructor body is
non-empty (my favorite destructor, when I can pull it off, is one with
an empty body), and (2) the body of the "outer" constructor (the one
which calls another constructor in its init list) may throw exceptions
(in particular, it must be non-empty).  So these examples are, even in
abbreviated pseudo-code, a bit complicated.

To illustrate point #3 made above, consider the following, where the
"member contract" (i.e. your version) is used:

    class Bar
    {
    public:
        Bar(args1) : init_list { do_stuff(); }
        Bar(args2) : Bar(args1) { do_more_stuff(); }
        ~Bar() { do_destructor_stuff(); }
    }

This can be converted to use the "constructor contract" (i.e. my
version) as follows:

    class Bar
    {
    public:
        Bar(args1) : init_list { do_stuff(); }
        Bar(args2) : Bar(args1)
            {
                try {
                    do_more_stuff();
                }
                catch(...) {
                    destroy();
                    throw;
                }
            }
        ~Bar() { destroy(); }
    private:
        void destroy() { do_destructor_stuff(); }
    }

I'll grant that the "member contract" version is simpler and clearer
for this example.  But the "constructor contract" code is not much
more complicated, and it contains no additional code duplication.  It
only adds a try/catch/throw sequence, and factors out the destructor
body into a private member function.

So the "member contract" style provides an interesting idiom, which
may sometimes result in slightly clearer code, but otherwise has no
strictly functional advantage over the "constructor contract" version.

On the other hand, consider the following example, which illustrates
what I consider the primary use of proposed extension.

Start with the following partial class definition (in strictly
conventional pseudo-C++):

    class Foo
    {
    public:
        Foo(args, more_args)
            : init_list
            {
                do_some_stuff();
                try {
                    do_some_more_stuff(more_args);
                }
                catch(...)
                {
                    undo_some_stuff();
                    throw;
                }
                assert(some_condition());
            }

        Foo(other_args, yet_more_args)
            : init_list
            {
                do_some_stuff();
                try {
                    do_yet_more_stuff(yet_more_args);
                }
                catch(...) {
                    undo_some_stuff();
                    throw;
                }
                assert(!some_condition());
            }

        ~Foo()
            {
                if (some_condition()) {
                    undo_some_more_stuff();
                } else {
                    undo_yet_more_stuff();
                }
                undo_some_stuff();
            }
    };


Assuming the stuff in "init_list" is sufficiently complicated and/or
tricky to justify a desire to avoid its (assumed) duplication, then
this could be rewritten with "full-body initialisers" using the
"constructor contract" as follows:

    class Foo
    {
    private:
        // This "ctor" only partially initialises the object
        Foo(init_args)
            : init_list
            {
                do_some_stuff();
            }
    public:
        Foo(args, more_args)
            : Foo(init_args)
            {
                try {
                    do_some_more_stuff(more_args);
                }
                catch (...)
                {
                    undo_some_stuff();
                    throw;
                }
                assert(some_condition());
            }

        Foo(other_args, yet_more_args)
            : Foo(init_args)
            {
                try {
                    do_yet_more_stuff(yet_more_args);
                }
                catch (...)
                {
                    undo_some_stuff();
                    throw;
                }
                assert(!some_condition());
            }

        ~Foo()
            {
                if (some_condition()) {
                    undo_some_more_stuff();
                } else {
                    undo_yet_more_stuff();
                }
                undo_some_stuff();
            }
    };

Note that this is just a straight-forward factoring of the "init_list"
and "do_some_stuff()" code from the two constructors into a newly
created, private "partial-constructor".  Very simple.  Very easy.

And, assuming that the "do_some_more_stuff()"/"do_yet_more_stuff()"
code must be executed before the class invariants hold (which was
meant to be implied), this code is broken for the "member contract"
version of initialiser lists.  As a matter of fact, the easiest way
that I can see of accomplishing this (in general) with the "member
contract" is to shove all the code into the private ctor; i.e. to
convert it to one of the cases where the "member contract" is
equivalent to the "constructor contract".  This can be considerably
more complicated and less readable:

    class Foo
    {
    private:
        Foo(int which_one, init_args, more_args, yet_more_args)
            : init_list
            {
                do_some_stuff();
                try {
                    if (which_one == 0) {
                        do_some_more_stuff(more_args);
                        assert(some_condition());
                    } else {
                        assert(which_one == 1);
                        do_yet_more_stuff(yet_more_args);
                        assert(!some_condition());
                    }
                }
                catch(...) {
                    undo_some_stuff();
                    throw;
                }
            }
    public:
        Foo(args)
            : Foo(0, init_args, more_args, dummy_yet_more_args)
            {
            }

        Foo(other_args, yet_more_args)
            : Foo(1, init_args, dummy_more_args, yet_more_args)
            {
            }

        ~Foo()
            {
                if (some_condition()) {
                    undo_some_more_stuff();
                } else {
                    undo_yet_more_stuff();
                }
                undo_some_stuff();
            }
    };

Forcing *all* the code, and arguments, into the private constructor
seems to work against the goal of simplifying the structure.  Thus I
think the "constructor contract" is the better choice.

In truth, I think the cases where the two versions of this proposal
can be distinguished are rare, especially if one has a good design.
So this discussion is about unusual cases of what would be a
not-very-often used feature that we might see in future versions of
C++.  But I certainly do recall cases where I would have liked to have
this feature (either version).

--Lon Willett


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.


[ 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: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 1999/09/07
Raw View
Lon Willett wrote:

> But the main disadvantage that I see to definition #3 is strictly
> conceptual.  I don't want my destructor to be called automatically until
> my constructor (whichever one was invoked) has successfully completed.
> This is how things work now, and it makes sense to me.

Short answer: if you are right, then you are wrong

Long answer:

But the main disadvantage that I see to your definition
is strictly conceptual.

I do want dtor to be called for an object x if an exception
is thrown and a ctor of x has successfully completed.

This is how things work now, and it makes sense to me.

--

Valentin Bonnard


[ 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: Lisa Lippincott <lisa_lippincott@advisories.com>
Date: 1999/09/08
Raw View
Lawrence Crowl <Lawrence.Crowl@eng.sun.com> wrote:
>     Foo::Foo( int a, bool b )
>     try
>     : Foo( a )
>     {
>         if ( b )
>             throw "Am I constructed?";  // Yes.
>     } // But no longer!
>     catch( ... )
>     {
>     }
>
>     construct with Foo( a )
>     throw
>     invisible catch for destructors
>     execute ~Foo()
>     invisible rethrow
>     visible catch
>     execute user handler
>
>
> Any problems?

With the function-try-block above, I would expect an (invisible) rethrow
after the user handler executes -- just as with ordinary constructors
using function-try-blocks.

My basic position at this point is that a constructor which uses
a full-object initializer should act very much as if it were the
constructor for a derived class.  That is, to figure out the semantics
of the constructor above, rewrite the class definition

class Foo
  {
   Foo( int a );
   Foo( int a, bool b );
   ...other declarations...
  };

to

class Foo
  {
   Foo( int a );
   friend class InventedDerivedClass;
   ...other declarations...
  };

class InventedDerivedClass: public Foo
  {
   InventedDerivedClass( int a, bool b );
   ~InventedDerivedClass()    {}
  };

And then, taking advantage of the empty body of the destructor of
the derived class, allow the derived constructor to be called as if it
were a constructor for the base class.

                                             --Lisa Lippincott


[ 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: Lawrence.Crowl@eng.sun.com (Lawrence Crowl)
Date: 1999/09/04
Raw View
Several posters have proposed varying semantics for what I will call
full-object initializers.  Full-object initializers are the
specification of an initializer with the same type as the constructor's
object type.  In previous posts, I pointed out some problems with those
semantics.  I propose here a reasonable "minimal extension" semantics
for such a construct.

Syntax Example

    Foo::Foo( int a, bool b )
    : sub_object_initializers
    { }

    Foo::Foo( int a )
    : Foo( a, false )
    { }

Why Have This Capability?

(1) Subroutines are a good thing.
(2) It allows programming without default arguments.
    (Default arguments are a bad idea.  The reasons are beyond the
    scope of this posting.  Accept it as a given that some of us think
    default arguments are not good.  At present, default arguments are
    only unavoidable in constructors.)

Semantic Constraints

(1) Upon return from a constructor, the object is constructed.
(2) Upon throw from a constructor, the object is not constructed.
    Any sub-objects that have been constructed are destructed.
(3) The implemenation of constructors must be consistent
    whether or not the implementation of other constructors is know.
(4) The constructors must be distinguishable via overloading rules.

Semantic Definition

(1) The initializer list may consist of either sub-object initializers
    or an initializer for the full object, but not both.
(2) The object is fully initialized upon entering the body of a
    constructor with a full-object initializer.
(3) An unhandled exception in the body of a constructor with a
    full-object initializer will result in a call to the full-object
    destructor before control returns to the caller (or to the catch
    block in the case of a try body).

Examples

Code examples mentioned and their corresponding sequence of actions
are:

    Foo::Foo( int a, bool b )
    : Foo( a )
    {
       if ( b )
           throw "Am I constructed?";   // Yes.
    } // But no longer!

    construct with Foo( a )
    throw
    invisible catch for destructors
    execute ~Foo()
    invisible rethrow

    Foo::Foo( int a, bool b )
    try
    : Foo( a )
    {
        if ( b )
            throw "Am I constructed?";  // Yes.
    } // But no longer!
    catch( ... )
    {
    }

    construct with Foo( a )
    throw
    invisible catch for destructors
    execute ~Foo()
    invisible rethrow
    visible catch
    execute user handler


Any problems?


  Lawrence Crowl                650-786-6146   Sun Microsystems, Inc.
                  Lawrence.Crowl@Eng.Sun.Com   901 San Antonio Road, UMPK16-303
                                               Palo Alto, California, 94303

[ 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: scorp@btinternet.com (Dave Harris)
Date: 1999/09/06
Raw View
Lawrence.Crowl@eng.sun.com (Lawrence Crowl) wrote:
> I propose here a reasonable "minimal extension" semantics for such
> a construct.
> [...]
> Any problems?

Looks good to me. Just to confirm the intent for this case:

    Foo::Foo( int a ) : bar1(0), bar2(a), bar3(0)
    {}

    Foo::Foo( int a, bool b )
    try
    : Foo( a )
    {
        // Normal case; fully constructed here.
    }
    catch( ... )
    {
        // Fully destructed here.
    }
    // Full constructed on exit.

If Foo has 3 sub-objects and Foo(int) throws, all are guaranteed
destructed before the catch in Foo(int,int) starts running, even if the
exception happens in the middle one. Only Foo(int) can reasonably know
what's to be done in that case.

Then the Foo(int,int)'s catch starts running with the object in a known
state (ie fully unconstructed) and can do whatever is required to recover.
If there are no more throws, the object is constructed on exit.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."
---
[ 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: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 1999/09/07
Raw View
Dave Harris wrote:

>     Foo::Foo( int a, bool b )
>     try
>     : Foo( a )
>     {
>         // Normal case; fully constructed here.
>     }
>     catch( ... )
>     {
>         // Fully destructed here.
>     }
>     // Full constructed on exit.

> If there are no more throws, the object is constructed on exit.

There is no way to recover. The object is _destructed_
on exit of a ctor catch block.

--

Valentin Bonnard
---
[ 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: Lon Willett <lwillett@my-deja.com>
Date: 1999/09/07
Raw View
In article <7qps9o$ia7$1@engnews2.Eng.Sun.COM>,
  Lawrence.Crowl@eng.sun.com (Lawrence Crowl) wrote:
>
> Several posters have proposed varying semantics for what I will call
> full-object initializers.  Full-object initializers are the
> specification of an initializer with the same type as the
> constructor's object type.  In previous posts, I pointed out some
> problems with those
> semantics.  I propose here a reasonable "minimal extension" semantics
> for such a construct.
...
> Semantic Constraints
>
> (1) Upon return from a constructor, the object is constructed.
> (2) Upon throw from a constructor, the object is not constructed.
>     Any sub-objects that have been constructed are destructed.
> (3) The implemenation of constructors must be consistent
>     whether or not the implementation of other constructors is know.
> (4) The constructors must be distinguishable via overloading rules.
>
> Semantic Definition
>
> (1) The initializer list may consist of either sub-object initializers
>     or an initializer for the full object, but not both.
> (2) The object is fully initialized upon entering the body of a
>     constructor with a full-object initializer.
> (3) An unhandled exception in the body of a constructor with a
>     full-object initializer will result in a call to the full-object
>     destructor before control returns to the caller (or to the catch
>     block in the case of a try body).

I don't agree with definition #3.  It seems to me that it is more
consistant with the way that constructors now work that the destructor
is never called for a throw from within the body of a constructor,
whether or not it uses a standard or the "full-object" initialisation
list.  Try/catch can be used as needed.  I suspect that this would also
make life easier for compiler writers (although that shouldn't be the
primary consideration).

This leaves open the possibility that one could implement a private
"constructor" that is used only for initialisation, and does not fully
construct the object (in violation of constraint #1).  While one could
argue that this is bad style, it might have its uses, and I don't really
see any benefits to forbidding it.

But the main disadvantage that I see to definition #3 is strictly
conceptual.  I don't want my destructor to be called automatically until
my constructor (whichever one was invoked) has successfully completed.
This is how things work now, and it makes sense to me.  "Full-object"
initilisation lists merely allow me to "extend" the body of a ctor, but
the bodies of both ctors should work within the same framework: i.e.
throws result in the destruction of sub-objects, but do not invoke the
dtor.

--Lon Willett


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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              ]