[Date Prev][Date Next][Thread Prev][Thread Next][Author Index][Date Index][Thread Index]

Re: Constructor bombs for copy objects



Wjr:  Please install an "xtech" alias at vlad, and/or find out why this
"R"eply to markm's letter was routed to xtech@vlad and bounced there.
Thanks.

Markm, dean:  You have probably already seen this.

============================================================================

>    ... Constructor bombs:
>     - Take over the normal call to the memory allocator,
>     - Arm themselves when they receive the memory from (operator) new, and:
>       - disarm themselves quietly if the constructor returns normally, or
>       - free the storage as the exception passes up out of the constructor.

> The big problem is that it doesn't deallocate the memory.  What about
> the fact that Derived's destructor is getting skipped?

Oops.  You're right.  I had forgotten that, though it was the memory leak
that originally led to the discovery that constructors were a special
problem, the CONSTRUCTOR_BOMB is also responsible for assuring that the
partially-constructed object is correctly dismantled.

It does this by doing a delete of the object (where I said above that it
"free[s] the storage..."), which:

 - destroys the object, starting with the destructor of the
   most-derived-class whose constructor has >started< executing,

 - working baseward in the normal manner, then

 - deleting the memory via the operator delete() of the class that
   allocated it.

So the consequence of not using CONSTRUCTOR_BOMBs is NOT just a memory
leak, but an accumulation of partially-constructed objects, with their
side-effects unrepaired.  And we SHOULD look into generating
CONSTRUCTOR_BOMBs now.

> Originally
> Michael convinced me that the Bomb package was doing the correct thing
> and Bjarne was making a mistake.  I now feel that the correct
> sequence, and the one we should recommend to the standardization
> effort, is:
> 	Base's destructor gets called
> 	The appropriate "operator delete ()" gets called.
> 
> When foo() BLASTs, we have a partially constructed Derived, & a fully
> constructed Base.  Any cleaning up of the partial construction should
> be done by planting, arming, and disarming bombs in Derived's
> constructor.  Then destructors can know they only need to deal with
> fully constructed objects.

[example deleted for brevity]

> Coding the constructor & destructor so the destructor knows how much
> to clean up is quite possible (Michael has a recommended technique),
> but is quite error prone and has some overhead.

The approach I described is still my preferred approach, and I take
issue with your characterization and conclusions.

Bombs should do only those things which must be done differently when
something fails.  Undoing successful early stages of construction should
be done by the same code that undoes them during normal destruction.

When coded with this in mind, my approach is considerably less error-prone
than attempting to replicate (perhaps several times) the appropriate amount
of destructor code as bombs to plant at each stage of the construction, and
it's a LOT easier to test.  Also, storing a constant in a variable as each
stage completes has considerably less overhead than planting, arming, and
disarming a bomb.

For those of you who have never seen it, here it my recommended approach
(with "this->progress = ..." changed to "progress = ..." for readability):

=========================================================================

enum FooProgress (None, ZipDone, ZapDone);

FooProgress progress;	// A private member variable.

Foo::
Foo() {
	PLANT_CONSTRUCTOR_BOMB();
	progress = None;

		x = zip();	// something which must be cleaned up

	progress = ZipDone;

		foo();		// a call which BLASTs in our example

		y = zap();	// something which must be cleaned up

	progress = ZapDone;

	bar();			// may BLAST
}


Foo::
~Foo() {
	switch (progress) {

	case ZapDone:
		unZap(y);
		progress = ZipDone;	// = ZapUndone...

	case ZipDone:
		unZip(x);
		progress = None;

	default:
	}
}

=========================================================================

If, in addition, foo(), zip(), or whatever needs something special done
when IT fails, you plant a bomb to do ONLY THAT.

Note that if bar() had BLASTed, the necessary action is exactly the normal
destruction of the object, while a hypothetical pre-zip() blast requres
only base-class destruction and memory deallocation.

Note also that the process extends trivially to as many stages of
construction as you need.  The general rule is:
 - Destroy in the opposite of the order you create.
 - Mark each place a step is complete and the next might BLAST.

(The assignments to "progress" in the destructor allow the caller of
 "delete" to retry if a destructor fails partway through destruction,
 after catching a BLAST from something within it and correcting the
 environment.  I expect this to be EXTREMELY rare, and better handled
 in the destructor itself (or perhaps even closer to the occurrence of
 the BLAST).  I included the assignments in the example only for
 completeness.)

Note, by the way, that as the coding style warps from ours towards that
our customers are likely to use, the one-liner "zip()/unZip()" calls will
tend to be replaced with more complex code, which will give my approach an
even greater advantage in the error-resistance competition.

===========================================

> Unfortunately, I see no way for the bomb package to cause Base's
> destructor to be invoked & to deallocate the object, but not to invoke
> Derived's destructor.  As far as I can tell, this can only be
> implemented by the language.  This means that there is nothing we can
> do to make the Bomb package compatable with what we should recommend.
> I also do not know whether or not there are any implementation
> problems with implementing this proposal efficiently.

Fortunately, what we >really< should recommend is what we recommended
before, so it's not a problem.  B-)  I'm sorry my lapse in the previous
letter wasted your time worrying about this.

> Interestingly, in the new A.R.M. book, this issue is left much more
> ambiguous than in Bjarne's original paper.  I suspect this is a result
> of the letter Michael & I sent him.  How ironic if Bjarne was more
> right in the first place.

Not a chance.  The irony has all rusted away.  B-)

> P.S.  What Bjarne proposes doing for member initializers which BLAST
> has always seemed exactly right, is unfortunately impossible to
> implement other than by the language, and therefore is an impossible
> to fix problem with the current Bomb package.

[or any other source-code add-on]

> This is non-fatal, as
> until this is fixed we must simply avoid member initializers which may
> BLAST.  (At the moment we cannot even generate members initializers
> from the Smalltalk translator, so this isn't a big worry.)

Check.

	michael