The Artima Developer Community
Sponsored Link

Weblogs Forum
Two Stage Construction in C++ versus Initializing Constructors

15 replies on 2 pages. Most recent reply: May 3, 2005 8:44 AM by Harrison Ainsworth

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 15 replies on 2 pages [ 1 2 | » ]
Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Two Stage Construction in C++ versus Initializing Constructors (View in Weblogs)
Posted: Apr 26, 2005 12:22 PM
Reply to this message Reply
Summary
I am working on the OOTL collections library right now, and I am facing a design dilemma: is it better to use initializing constructors or to use two stage construction.
Advertisement
In C++ there is one huge imperfection which has really got me frustrated lately, the fact that constructors are not inherited. I think this was done because C++ uses constructors as implicit cast operators (this in of itself is another big imperfection!)

I am trying to construct sophisticated object oriented libraries with many levels of inheritance, and the fact that I have to write out explicitly trivial constructors over and over again is driving me nuts.

In general I can't help but wonder, what is so bad about two stage construction. Here is my list of pros and cons:

Cons

  1. Possible performance penalty, when default initialization steps are taken unneccessarily.
  2. Classes which should not be default constructed will require checks requiring an initialization variable to make sure that they are initialized before used.

Pros

  • Designs with deep inheritance trees can be expressed with significantly less code
  • Wrapper classes (e.g. contract classes or veneers [see Matthew Wilson's book Imperfect C++ Chapter 21]) are much easier to implement, and become more viable
  • There is no chance of programmer errors within multiple initialization lists (see Matthew Wilson's book Imperfect C++ section 2.3.2 on Member Ordering Dependencies)
  • No chance of incorrect casts silently generated by the compiler
  • Easier to trace, debug and understand code
Con #1 should be a non-issue much of the time, because a sophisticated optimizer can easily remove redundant operations. Con #2 can be dealt with relative ease and efficiency using contracts to verify the initializing. Interestingly enough contracts are much easier to implement if classes don't have initializing constructors.

The reason all of this has become such an issue for me is that I want to seperate the OOTL implementation of standard classes (like lists or arrays) into four levels: implementation, contract, extension, and final, where each one inherits from the previous.

  • The implementation class represents a basic implementation of the core data type.
  • the contract is a class which wraps (i.e. inherits from) the implementation and provides verification of the preconditions, postconditions and invariants
  • The extension wraps the contract adding extra utility functions which can be trivially derived from the core implementation functions (e.g. syntactic sugar)
  • The final version is a class which conditionally (depending on compile-time constants) creates a class for the user to use, which either applies the contract to the implementation or not
So for example where the STL defines a single class like std::string, the OOTL will be creating a hierarchy of four classes: ootl::string_impl, ootl::string_contract, ootl::string_extensions, ootl::string. Clearly writing out constructors over and over is not something that I look forward to, nor does it make sense from an engineering standpoint.

This is where I am at so far. Any thoughts, comments or additions would be welcome!


Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 26, 2005 2:18 PM
Reply to this message Reply
> In general I can't help but wonder, what is so bad about
> two stage construction. Here is my list of pros and cons:
>
> <p>
> <h3>Cons</h3>
> <ol>
> <li>Possible performance penalty, when default
> initialization steps are taken unneccessarily.</li>
> <li>Classes which should not be default constructed will
> require checks requiring an initialization variable to
> make sure that they are initialized before used.

And a third: you can't have const and/or reference members which means you lose the power of the compiler to enforce many design decisions. (Since you've been kind enough to mention IC++, that's discussed in part in section 2.5)

> </li>
> </ol>

> <h3>Pros</h3>
> <ul>
> <li>Designs with deep inheritance trees can be expressed
> with significantly less code</li>

Depends where the "less code" is. IMO "less code" is client context is much more important than "less code" in libraries, although "least code" in both is obviously the nicest (and often the most elegant) solution.

> <li>Wrapper classes (e.g. contract classes or veneers [see
> Matthew Wilson's book Imperfect C++ Chapter 21]) are much

It's a gem. I heartily recommend everyone go out and get a copy right now! LOL

> easier to implement, and become more viable</li>

Excellent point. But if your classes have largely conformant interfaces, then it's possible to write specialised veneer templates that avoid the nasties of constructor argument forwarding (Ch 23)

> <li>There is no chance of programmer errors within
> multiple initialization lists (see Matthew Wilson's book
> Imperfect C++ section 2.3.2 on Member Ordering
> Dependencies)</li>

Yes, but that's something of a specious Pro, since losing the power of initialiser lists is a far greater problem (at least IMO). Incidentally, there's a way to still use initialiser lists in hierarchies, using the MUMI idiom ("Handling Parameter Sets in Member Initialiser Lists: With A Little Help From MUMI!", CUJ Experts Forum, http://www.cuj.com/documents/s=9223/cujexp0406wilson/)

> <li>No chance of incorrect casts silently generated by the
> compiler</li>

Huh??

> <li>Easier to trace, debug and understand code</li>

I'm not sure that's correct. There're fewer better understandable things in C++ than classes supporting (weak?) immutable RAII. One constructs it, uses it, and it cleans itself up.

> </ul>
>
> Con #1 should be a non-issue much of the time, because a
> sophisticated optimizer can easily remove redundant
> operations.

I agree that it should be largely irrelevant most of the time, but I don't agree that an optimiser will always (or even mostly) be able to remove things that we _know_ are logically redundant, but it doesn't _see_ as physically redundant. I think a case by case analysis would have to be undertaken.

> Con #2 can be dealt with relative ease and
> efficiency using contracts to verify the initializing.

True, but it's mucky stuff.

> Interestingly enough contracts are <i>much</i> easier to
> implement if classes don't have initializing constructors.

Intriguing ... Have you some examples to demonstrate this?

> <p>
> The reason all of this has become such an issue for me is
> that I want to seperate the OOTL implementation of
> standard classes (like lists or arrays) into four levels:
> implementation, contract, extension, and final, where each
> one inherits from the previous.
>
> <ul>
> <li>The implementation class represents a basic
> implementation of the core data type.</li>
> <li>the contract is a class which wraps (i.e. inherits
> from) the implementation and provides verification of the
> preconditions, postconditions and invariants</li>
> <li>The extension wraps the contract adding extra utility
> functions which can be trivially derived from the core
> implementation functions (e.g. syntactic sugar)</li>
> <li>The final version is a class which conditionally
> (depending on compile-time constants) creates a class for
> the user to use, which either applies the contract to the
> implementation or not</li>
> </ul>


Very interesting. What's the rationale for the separation? What does each level do? Which of the four are "usable" to the client coder?

Why is inheritance necessary?

The third level sounds a bit iffy? Are you not taking the approach of favouring non-member functions where possible? (whispers ... "shims" <g>)

>
> So for example where the STL defines a single class like
> <code>std::string</code>, the OOTL will be creating a
> hierarchy of four classes: <code>ootl::string_impl,
> ootl::string_contract, ootl::string_extensions,
> ootl::string</code>. Clearly writing out constructors over
> and over is not something that I look forward to, nor does
> it make sense from an engineering standpoint.
> <p>
> This is where I am at so far. Any thoughts, comments or
> additions would be welcome!

I am the kind of thickie that needs to see concrete examples to understand the ramifications of discussions such as this. If you can post some examples - the string one'd be perfect - that'd really help me comprehend the issue(s).

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 26, 2005 4:30 PM
Reply to this message Reply
Hi Matthew,

Thanks for the quick and detailed response!

> And a third: you can't have const and/or reference members
> which means you lose the power of the compiler to enforce
> many design decisions. (Since you've been kind enough to
> mention IC++, that's discussed in part in section 2.5)

Good point.

> Depends where the "less code" is. IMO "less code" is
> client context is much more important than "less code" in
> libraries,

I have to agree.

> although "least code" in both is obviously the
> nicest (and often the most elegant) solution.

I am trying to minimize the library code.

> It's a gem. I heartily recommend everyone go out and get a
> copy right now! LOL

Yes they should!

> Excellent point. But if your classes have largely
> conformant interfaces, then it's possible to write
> specialised veneer templates that avoid the nasties of
> constructor argument forwarding (Ch 23)

This is indeed a passable solution in some contexts.

> Yes, but that's something of a specious Pro, since losing
> the power of initialiser lists is a far greater problem
> (at least IMO).

I just don't see the power of initialiser lists.

> Incidentally, there's a way to still use
> initialiser lists in hierarchies, using the MUMI idiom
> ("Handling Parameter Sets in Member Initialiser Lists:
> With A Little Help From MUMI!", CUJ Experts Forum,
> http://www.cuj.com/documents/s=9223/cujexp0406wilson/)

Thanks for pointing that out.

> > <li>No chance of incorrect casts silently generated by
> the
> > compiler</li>
>
> Huh??

Ummm... that came out wrong, I shouldn't have stated it so strongly.

I meant that we can reduce the chance of implicit casts happening undetected where you don't intend them to. I have found implicit casts in general to be a serious danger of C++ programming. Perhaps this is less so, for people who work with C++ on a more day to day basis, and are used to the rather complicated casting rules.

> I'm not sure that's correct. There're fewer better
> understandable things in C++ than classes supporting
> (weak?) immutable RAII. One constructs it, uses it, and it
> cleans itself up.

This could be debated, but I know you are a big fan of RAII. I believe RAII obscures the what is actually going on which is often what is desired, but not always. I also have a big gap in my C++ knowledge when it comes to constructor throwing during construction, and member initializing.

> I agree that it should be largely irrelevant most of the
> time, but I don't agree that an optimiser will always (or
> even mostly) be able to remove things that we _know_ are
> logically redundant, but it doesn't _see_ as physically
> redundant. I think a case by case analysis would have to
> be undertaken.

Good point.

> > Con #2 can be dealt with relative ease and
> > efficiency using contracts to verify the initializing.
>
> True, but it's mucky stuff.

I am working on demuckification it but I need to not have to worry about forwarding my constructors all the time.

> > Interestingly enough contracts are <i>much</i> easier
> to
> > implement if classes don't have initializing
> constructors.
>
> Intriguing ... Have you some examples to demonstrate
> this?

Sorry I overstated my point again. The only advantage (I can think of anyway) is you don't have to rewrite all of the constructors. Well for me that is significant, but perhaps not for others.

> Very interesting. What's the rationale for the separation?

Reuse, and separation of concerns.

> What does each level do?

Contract classes separate the concern of contract verification from actual implementation details. For instance it might have its own private fields and helper member functions. It can be easily reused on different implementations as long as they have matching interfaces.

The extension class separates syntactic sugar outside of the class. This means that derived functions can be easily reused on any matching class.

> Which of the four are "usable" to
> the client coder?

The final class is most usable. The contract layer and extension layer can be reused with different implementation classes, so it can also be used by a client coder

> Why is inheritance necessary?

The alternative would be an increasing mess of forwarding functions.

> The third level sounds a bit iffy? Are you not taking the
> approach of favouring non-member functions where possible?
> (whispers ... "shims" <g>)

I want to try and use member functions as much as I can.

> I am the kind of thickie that needs to see concrete
> examples to understand the ramifications of discussions
> such as this. If you can post some examples - the string
> one'd be perfect - that'd really help me comprehend the
> issue(s).

Yes, I will work that up ASAP.

Thanks again for your comments!

Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 26, 2005 4:49 PM
Reply to this message Reply
> Hi Matthew,
>
> Thanks for the quick and detailed response!

As always, you're welcome. :-)

[snippety-snip]

> > Excellent point. But if your classes have largely
> > conformant interfaces, then it's possible to write
> > specialised veneer templates that avoid the nasties of
> > constructor argument forwarding (Ch 23)
>
> This is indeed a passable solution in some contexts.

Well, at the risk of editorialising your response, I think it's a great solution in some contexts. In the general case it's a horrible thing, requiring exponential amount of forwarding ctors to be written (see Ch 23). But in specific cases it can be very easy, apposite and clear to understand. More than just 'passable', then.

<end of peremptory editorialisation :-) >

> > Yes, but that's something of a specious Pro, since
> losing
> > the power of initialiser lists is a far greater problem
> > (at least IMO).
>
> I just don't see the power of initialiser lists.

Means you can use Immutable RAII, which is far easier to code and to understand than Mutable RAII.

> > Incidentally, there's a way to still use
> > initialiser lists in hierarchies, using the MUMI idiom
> > ("Handling Parameter Sets in Member Initialiser Lists:
> > With A Little Help From MUMI!", CUJ Experts Forum,
> > http://www.cuj.com/documents/s=9223/cujexp0406wilson/)
>
> Thanks for pointing that out.

I'm always happy to plug my own work. ;)

> > > <li>No chance of incorrect casts silently generated
> by
> > the
> > > compiler</li>
> >
> > Huh??
>
> Ummm... that came out wrong, I shouldn't have stated it so
> strongly.
>
> I meant that we can reduce the chance of implicit casts
> happening undetected where you don't intend them to. I
> have found implicit casts in general to be a serious
> danger of C++ programming. Perhaps this is less so, for
> people who work with C++ on a more day to day basis, and
> are used to the rather complicated casting rules.

Sorry, mate, I'm still unclear what you mean. If you're referring to the undesired effects of implicit construction, then this is easily and completely obviated by use of explicit. If you don't mean this, then I must apologise for being a dolt, as I just don't follow what you mean.

> > I'm not sure that's correct. There're fewer better
> > understandable things in C++ than classes supporting
> > (weak?) immutable RAII. One constructs it, uses it, and
> it
> > cleans itself up.
>
> This could be debated, but I know you are a big fan of
> RAII. I believe RAII obscures the what is actually going
> on which is often what is desired, but not always.

Unclear. Can you rephrase?

> I also
> have a big gap in my C++ knowledge when it comes to
> constructor throwing during construction, and member
> initializing.

Well, it's both fearsomely complex and elegantly simple. I prefer to remember the simple perspective, which is: When an exception is thrown, fully-constructed objects are destroyed.

(Nothing else is)

> > > Con #2 can be dealt with relative ease and
> > > efficiency using contracts to verify the
> initializing.
> >
> > True, but it's mucky stuff.
>
> I am working on demuckification it but I need to not have
> to worry about forwarding my constructors all the time.

I look forward to tasting the results of your search for demuckification. ;)

> > What does each level do?
>
> Contract classes separate the concern of contract
> verification from actual implementation details. For
> instance it might have its own private fields and helper
> member functions. It can be easily reused on different
> implementations as long as they have matching interfaces.
>
> The extension class separates syntactic sugar outside of
> the class. This means that derived functions can be easily
> reused on any matching class.

Is this a Bolt-in then? (IC++ Ch 22)

> > Which of the four are "usable" to
> > the client coder?
>
> The final class is most usable. The contract layer and
> extension layer can be reused with different
> implementation classes, so it can also be used by a client
> coder

Gotcha. Smells like a Bolt-in. If it is, I look forward to seeing how it all works. (And if it isn't.)

> > The third level sounds a bit iffy? Are you not taking
> the
> > approach of favouring non-member functions where
> possible?
> > (whispers ... "shims" <g>)
>
> I want to try and use member functions as much as I can.

Is there a particular motivation for this, or do you prefer member to non-member fns? I prefer the latter, but I acknowledge that they make for a more ephemeral delineation of a type's 'functionality'. Is that where your preference, if it exists, comes from?

> > I am the kind of thickie that needs to see concrete
> > examples to understand the ramifications of discussions
> > such as this. If you can post some examples - the
> string
> > one'd be perfect - that'd really help me comprehend the
> > issue(s).
>
> Yes, I will work that up ASAP.

Cool. That'll help a lot.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 27, 2005 7:33 AM
Reply to this message Reply
> Well, at the risk of editorialising your response, I think
> it's a great solution in some contexts. In the general
> case it's a horrible thing, requiring exponential amount
> of forwarding ctors to be written (see Ch 23). But in
> specific cases it can be very easy, apposite and
> clear to understand. More than just 'passable', then.

You are correct for editing my statement. I thought there were implicit casting pecularities that arose from template constructors. This does not appear to be the case, therefore the solution is indeed excellent in specific cases, and horrible and others.

> Means you can use Immutable RAII, which is far
> easier to code and to understand than Mutable RAII.

Good point.

> Sorry, mate, I'm still unclear what you mean. If you're
> referring to the undesired effects of implicit
> construction, then this is easily and completely obviated
> by use of explicit. If you don't mean this,
> then I must apologise for being a dolt, as I just don't
> follow what you mean.

Yes, correct use of explicit can solve some problems of unintended casts but not all. Even using explicit It is still frightfully easy to create a hopelessly tangled web of implicit conversion rules (I am sure you have run into this more than once), and it is even something that I am constantly doing by accident.

> > > I'm not sure that's correct. There're fewer better
> > > understandable things in C++ than classes supporting
> > > (weak?) immutable RAII. One constructs it, uses it,
> and
> > it
> > > cleans itself up.
> >
> > This could be debated, but I know you are a big fan of
> > RAII. I believe RAII obscures the what is actually
> going
> > on which is often what is desired, but not always.
>
> Unclear. Can you rephrase?

RAII code hides control flow in constructors and destructors. This means that tracing program flow manually can be very tricky because you have to take into account the order of construction and destruction of member variables and inherited classes, which is AFAIK not defined. The big result of this is that RAII code is not always deterministic! Debugging that can be a mess. It is easy to blame the engineers for not using the RAII idiom correctly, but I blame the idiom and the language, not the engineer.


> I look forward to tasting the results of your search for
> demuckification. ;)

:-)

> Is this a Bolt-in then? (IC++ Ch 22)

Just read that section and the answer is yes.

I find the delineation between Bolt-in and Veneer in your book somewhat academic. In my work I can't see how differentiating between a Bolt-in and Veneer would be important? Are both Bolt-in's and Veneers simply refinements of the CRTP? Perhaps that would make a good Blog entry for you? I think the Artima community would enjoy reading about Bolt-in's, Veneers and CRTP (Veneers for Venners?)

> > > The third level sounds a bit iffy? Are you not taking
> > the
> > > approach of favouring non-member functions where
> > possible?
> > > (whispers ... "shims" <g>)
> >
> > I want to try and use member functions as much as I
> can.
>
> Is there a particular motivation for this, or do you
> prefer member to non-member fns? I prefer the latter, but
> I acknowledge that they make for a more ephemeral
> delineation of a type's 'functionality'. Is that where
> your preference, if it exists, comes from?

That is one part of it. Member functions give us one of the major advantages of the OOP methodology which is bundling of code and data together in one place. This make code easier to work with and manage.

> > Yes, I will work that up ASAP.
>
> Cool. That'll help a lot.

Kristian Dupont

Posts: 22
Nickname: chryler
Registered: Dec, 2003

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 27, 2005 11:23 AM
Reply to this message Reply
>Member functions give us one of the major advantages of the
>OOP methodology which is bundling of code and data together
>in one place. This make code easier to work with and manage.

I find the opposite to be true, actually.
I think it is because I tend have member functions accessing private stuff all the time. They are allowed of course, but quite often, you can accomplish the same with an external function which go through the public interface. This makes the code more adaptable and easier to refactor.
With namespaces, it is easy to group classes and functions together, and I think that classes should be as small as possible, resulting in stronger and easier to maintain invariants.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 27, 2005 11:47 AM
Reply to this message Reply
I have just written a new post which attempts to explain in more detail what I am attempting to do by separating classes up into implementation, contract and extension parts.

http://www.artima.com/weblogs/viewpost.jsp?thread=106760

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 27, 2005 11:57 AM
Reply to this message Reply
> >Member functions give us one of the major advantages of
> the
> >OOP methodology which is bundling of code and data
> together
> >in one place. This make code easier to work with and
> manage.
>
> I find the opposite to be true, actually.
> I think it is because I tend have member functions
> accessing private stuff all the time. They are allowed of
> course, but quite often, you can accomplish the same with
> an external function which go through the public
> interface. This makes the code more adaptable and easier
> to refactor.

I am trying to accomplish something similar through extension classes (a.k.a. bolt-ins / wrapper classes) (see my latest blog entry at http://www.artima.com/weblogs/viewpost.jsp?thread=106760 )

> With namespaces, it is easy to group classes and functions
> together, and I think that classes should be as small as
> possible, resulting in stronger and easier to maintain
> invariants.

I agree small classes are much better. However, I believe that we can leverage inheritance properly to achieve small classes.

IMO namespaces have a lot of problems as a substitute for classes. They can't be inherited, they lack visibility specifiers, they lack well-defined initializiation and finalization sections, and they can't be passed type parameters.

Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 27, 2005 2:06 PM
Reply to this message Reply
> > Sorry, mate, I'm still unclear what you mean. If you're
> > referring to the undesired effects of implicit
> > construction, then this is easily and completely
> obviated
> > by use of explicit. If you don't mean
> this,
> > then I must apologise for being a dolt, as I just don't
> > follow what you mean.
>
> Yes, correct use of explicit can solve some
> problems of unintended casts but not all. Even using
> explicit It is still frightfully easy to
> create a hopelessly tangled web of implicit conversion
> rules (I am sure you have run into this more than once),
> and it is even something that I am constantly doing by
> accident.

Actually, no. I never have any such problems. Truly! :-)

> > > > I'm not sure that's correct. There're fewer better
> > > > understandable things in C++ than classes
> supporting
> > > > (weak?) immutable RAII. One constructs it, uses it,
> > and
> > > it
> > > > cleans itself up.
> > >
> > > This could be debated, but I know you are a big fan
> of
> > > RAII. I believe RAII obscures the what is actually
> > going
> > > on which is often what is desired, but not always.
> >
> > Unclear. Can you rephrase?
>
> RAII code hides control flow in constructors and
> destructors. This means that tracing program flow manually
> can be very tricky because you have to take into account
> the order of construction and destruction of member
> variables and inherited classes, which is AFAIK not
> defined.

It is very precisely defined:
- Member variables are initialised in their order within the class definition. (Not the initialiser list. G++ warns of this; others do not.)
- Base classes are done according to their left-most ordering in a DAG.

> The big result of this is that RAII code is not
> always deterministic!

Not so.

> Debugging that can be a mess. It is
> easy to blame the engineers for not using the RAII idiom
> correctly, but I blame the idiom and the language, not the
> engineer.

I concede that it's possible that there is some fundamental flaw in this regard, but I have to say I've never had a problem with RAII, and don't see any failings.

> > Is this a Bolt-in then? (IC++ Ch 22)
>
> Just read that section and the answer is yes.

Cool.

> I find the delineation between Bolt-in and Veneer in your
> book somewhat academic. In my work I can't see how
> differentiating between a Bolt-in and Veneer would be
> important? Are both Bolt-in's and Veneers simply
> refinements of the CRTP? Perhaps that would make a good
> Blog entry for you? I think the Artima community would
> enjoy reading about Bolt-in's, Veneers and CRTP (Veneers
> for Venners?)

I agree that the delineation is not the best. When I re-read it recently, I felt that I needed to make it clearer.

I unambiguously recognise one or the other when presented with it, but find it hard to give a 100% unequivocal definition.

Bjorn and I had a conversation some months ago where we discussed veneers and bolt-ins, and we identified some really great points. We're planning on writing that up at some point, although When? is always the big question. ;)

Max Lybbert

Posts: 314
Nickname: mlybbert
Registered: Apr, 2005

Proposed solution Posted: Apr 28, 2005 4:00 PM
Reply to this message Reply
The Stroustrup Approved technique (from his FAQ -- http://www.research.att.com/~bs/bs_faq2.html#virtual-ctor ) is to use an object factory. After giving your situation some thought, this is where I would start (NOTE: I have not run this through a compiler, it may have syntax errors):


class Attorney
{ public:
static template<class C, class Boiler_Plate>
C make_contract(int a, double b, std::string c,
Boiler_Plate* bp = default_boiler)
{ // fiddle with bp
return new factory_impl<C>(a, b, c, bp);
};
};

[code]factory_impl
would then be specialized according to class C. The reason for not handling everything in make_contract is that the factory pretty much must be specialized according to class C, and specializing make_contract may be too early (there may be duplication between specializations if you have different kinds of bps). On top of that, it’s easier to handle specialization according to Boiler_Plates in make_contract and specialization according to C’s in factory_impl.

After working with this a little, you should be able to determine if the extra step is needed.

Of course, there is no need to templatize. The C++ Programming­ Language (the book) has an example of a non-templatized factory in section 12.4.4. You could even stop using static methods, so that even your object factories could be polymorphic. However, I prefer:

Attorney::make_contract<my_class> my_class_contract(2, 3.141, s, base_boiler);

to:

Attorney::make_contract(2, 3.141, s, fully_constructed_class_C, base_boiler);

or:

Attorney atty(...);
atty.make_contract(...);
.

Especially since the templatized version can create a contract before you have a fully-constructed object of class C (you could even call the templatized version as part of class C’s constructor).

The real question is how to write factory_impl. You could have it call class C’s real constructor, but then other people would want to call it as well, and that’s likely going to lead to trouble.

I’m not sure if you can declare a template:

template<class C> void make_contract(...) friend class C ...

which you'd need to do if you wanted to create an empty C and initialize it through calling C’s methods. I don’t like that route anyway, since it’s just a roundabout way of doing what you’ve already proposed – constructing empty Cs and then filling them with C.inits. The problem I see in this approach is that init can fail, leaving you with constructed, but not initialized Cs -- that is Cs that may violate the invariant.

I think the best move is to either:

(A) have C constructors that Attorney calls to really construct the object, and constructors that users call, and which then call Attorney (which then calls the real constructor) to create the object; or,

(B) make factory_impl a class, and have a C constructor in the form of C(const factory_impl<C>&). For instance, my_class would have a constructor my_class::my_class(const factory_impl<my_class>). The last line in make_contract would then change from

return new factory_impl<C>(a, b, c, bp);

to

return new C(factory_impl<C>(a, b, c, bp));

This is an example of the Curiously Recurring Template Pattern.

Personally, I think I’d be more likely to get (A) right, although I think (B) has a certain kind of charm. Then again, you could combine the two. ...

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Proposed solution Posted: Apr 28, 2005 8:54 PM
Reply to this message Reply
> The Stroustrup Approved technique (from his FAQ --
> http://www.research.att.com/~bs/bs_faq2.html#virtual-ctor
> ) is to use an object factory.

The problem I have is not with missing virtual constructors, so I don't see how the FAQ entry applies. My problem is that when inheriting I have to repetitively forward initializing constructor parameters to the base class, for instance:


struct Base {
Base(int x) : m(x) { }
int m;
};

struct Derived : public Base {
};

Derived d(42); // does not compile


I don't see how the object factory solution applies here.

> ... The problem I see in this approach is that init can
> fail leaving you with constructed, but not initialized C

This is a good point. My experience however is that the number of classes which require a constructor with one or more parameters in order to be initialized is exceedingly rare, and even then often a regrettable design choice. For instance almost every class in the C++ standard library is default constructible.

This is as far as I am concerned the only truly significant trade off, with a two-stage construction design.

Max Lybbert

Posts: 314
Nickname: mlybbert
Registered: Apr, 2005

Re: Two Stage Construction in C++ versus Initializing Constructors Posted: Apr 29, 2005 7:24 AM
Reply to this message Reply
Aaahg! After writing my example, but before writing my explanation, I forgot that the factory wasn’t meant to make Cs, but C_contracts (or contract<C>s) -- it is name Attorney, after all.

That whole discussion about constructors was wrong. The factory should return a fully-constructed/initialized contract<C>, which you can use however you’re using C_contracts right now -- as a parameter to C::C(), inside the constructor (maybe to get Aspect oriented Programming techniques), etc.

Max Lybbert

Posts: 314
Nickname: mlybbert
Registered: Apr, 2005

Re: Proposed solution Posted: Apr 29, 2005 7:29 AM
Reply to this message Reply
What I'm understanding is that you have various classes inheriting from base classes, but since constructors aren't inherited you have to rewrite the same constructor over and over.

Now, yes, I realize that isn't exactly the same as virtual constructors, since "virtual" means more than "can be overridden." However, using an object factory makes it possible to write a generalized constructor, and inheirit from that, and (since the generalized constructor is really a method of another class), you get your inheritence.

My point is that the factory can set up something of a finite state machine, and depending on the arguments sent it (and the method called), you can get the effect you want.

Terje Slettebø

Posts: 205
Nickname: tslettebo
Registered: Jun, 2004

Re: Proposed solution Posted: May 2, 2005 10:09 AM
Reply to this message Reply
Christopher Diggins:

> The problem I have is not with missing virtual
> constructors, so I don't see how the FAQ entry applies. My
> problem is that when inheriting I have to repetitively
> forward initializing constructor parameters to the base
> class, for instance:
>
> struct Base {
> Base(int x) : m(x) { }
> int m;
> };
>
> struct Derived : public Base {
> };
>
> Derived d(42); // does not compile

Well, if you can wait until, oh, 2011 (*) or so, :) then we might get constructor inheritance in the language (http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2004/n1583.pdf). Then again, it may also not make it for C++0x.

Regards,

Terje

(*) The schedule for C++0x is 2009, and then compiler vendors may need a few years to catch up.

Max Lybbert

Posts: 314
Nickname: mlybbert
Registered: Apr, 2005

OK looking it over Posted: May 2, 2005 4:49 PM
Reply to this message Reply
My original suggestion had a boneheaded move in it, and doesn't rely on the presence of an object factory.

First, the boneheaded move was passing a pointer to a template. The template would have been just as happy with a class, and the resulting implementation would have been more effecient (discouting code bloat), as passing a pointer either requires (a) memberwise copy the first time values are snatched, or (b) extra indirection every time values or funstions are called.

It would have made more sense to pass the boiler_plate object as an object, and include a list of typedefs to access each value or function factored into that class in the Contract class. That is, factor out the common code into the member class "bp," and then typedef Contract.bp.getValue(int) as the shorter Contract.getValue(int). However that list of typedefs would be just as long as the constructor we don't want to rewrite. Then again, we are talking about commonly used members and methods, so a pre-processor macro (via Boost's extra-special pre-processor macros, perhaps) might be acceptable here. Templates could possibly do the job via metaprogramming, but I haven't wuite grokked the whole concept yet.

Second, this refactoring has everything to do with that boiler_plate object and nothing to do with object factories. Stroustrup likes object factories because they give a single interface for creating new objects, while hiding several details that can be hidden in some instances. They don't save the programmer from typing. Typedefs and pre-processor macros (and templates) are the tools for saving the programmer from excessive typing.

Flat View: This topic has 15 replies on 2 pages [ 1  2 | » ]
Topic: I Hope I Work with Mediocre People Previous Topic   Next Topic Topic: Bolt-Ins for Contract and Extension Classes

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use