I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

Yes, #2 is safe and legal (if you get to it), but:

  • The function as a whole is not safe.

  • It's a bad habit to get into.

The C++ standard explicitly allows this code. The reference rt is not invalidated by the in-place destruction and reconstruction. (Of course, you can't use t or rt between the call to t.~T() and the placement new, because during that time no object exists. We're also forced to assume that T::operator&() hasn't been overloaded to do something other than return the object's address.)

The reason we say #2 is safe "if you get to it" is that f() as a whole may not be exception-safe.

The function is not safe. If T's constructor may throw in the new (&t) T(2) call, then f() is not exception-safe. Consider why: If the T(2) call throws, then no new object has been reconstructed in the t memory area, yet at the end of the function, T::~T() is naturally called (since t is an automatic variable) and "t is destroyed again," just as the comment says. That is, t will be constructed once but destroyed twice (oops). This is likely to create unpredictable side effects, such as core dumps.

Guideline

graphics/guideline_icon.gif

Always endeavor to write exception-safe code. Always structure code so that resources are correctly freed and data is in a consistent state even in the presence of exceptions.


This is a bad habit. Ignoring the exception-safety issues, the code happens to work in this setting because the programmer knows the complete type of the object being constructed and destroyed. That is, the object was a T and is being destroyed and reconstructed as a T.

This technique is rarely, if ever, necessary in real code and is a very bad habit to get into, because it's fraught with (sometimes subtle) dangers if it appears in a member function.



// Can you spot the subtle problem? 


//


void T::DestroyAndReconstruct( int i )


{


  this->~T();


  new (this) T(i);


}


Now is this technique safe? In general, no. Consider the following code.



class U : public T { /* ... */ }; 


void f()


{


  /*AAA*/ t(1);


  /*BBB*/& rt = t;


  //--- #1: do something with t or rt ---


  t.DestroyAndReconstruct(2);


  //--- #2: do something with t or rt ---


}// t is destroyed again


If "/*AAA*/" is "T", the code in #2 will still work, even if "/*BBB*/" is not "T" (it could be a base class of T).

If "/*AAA*/" is "U", all bets are off, no matter what "/*BBB*/" is. Probably, the best you can hope for is an immediate core dump, because the call t.DestroyAndReconstruct() "slices" the object. Here, the slicing issue is that t.DestroyAndReconstruct() replaces the original object with another object of a different type梩hat is, a T instead of a U. Even if you're willing to write nonportable code, there's no way of knowing whether the object layout of a T is even usable as a U when it's superimposed in memory where a U used to be. Chances are good that it's not.

Don't go there. This is never a good practice.

Guideline

graphics/guideline_icon.gif

Avoid the "dusty corners" of a language; use the simplest techniques that are effective.


This Item has covered some basic safety and slicing issues of in-place destruction and reconstruction. This sets the stage for the followup question in Item 41.

    I l@ve RuBoard Previous Section Next Section