I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

Short answer: In most cases, it's bad only if, without the test, self-assignment is not handled correctly. Although this check won't protect against all possible abuses, in practice it's fine as long as it's being done for optimization only. In the past, some have suggested that multiple inheritance affects the correctness of this problem. This is not true, and it's a red herring.

Exception Safety (Murphy)

If T::operator=() is written using the create-a-temporary-and-swap idiom (see page 47), it will be both strongly exception-safe and not have to test for self-assignment. Period. Because we should normally prefer to write copy assignment this way, we shouldn't need to perform the self-assignment test, right?

Guideline

graphics/guideline_icon.gif

Never write a copy assignment operator that relies on a check for self-assignment in order to work properly; a copy assignment operator that uses the create-a-temporary-and-swap idiom is automaticially both strongly exception-safe and safe for self-assignment,


Yes and no. It turns out that there are two potential efficiency costs when not checking for self-assignment.

  • If you can test for self-assignment, then you can completely optimize away the assignment

  • Often making code exception-safe also makes it less efficient (a.k.a. the "paranoia has a price principle").

In practice, if self-assignment happens often (rare, in most programs) and the speed improvement gained by suppressing unnecessary work during self-assignment is significant to your application (even rarer, in most programs), check for self-assignment.

Guideline

graphics/guideline_icon.gif

It's all right to use a self-assignment check as an optimization to avoid needless work.


Operator Overloading (Machiavelli?)

Because classes may provide their own operator&(), the test in question may do something completely different than is intended. This comes under the heading of "protecting against Machiavelli," though; presumably, the writer of T::operator=() knows whether his class also overloads T::operator&(). Of course, a base class could also provide operator&() in a way that breaks this test.

Note that while a class may also provide a T::operator!=(), this is irrelevant because it can't interfere with the "this != &other" test. The reason is that you can't write an operator!=() that takes two T* parameters, because at least one parameter of an overloaded operator must be of class type.

Postscript #1:

As a bonus, here's a little code joke. Believe it or not, it's been tried by well-meaning (although clearly misguided) coders.



T::T( const T& other ) 


{


  if( this != &other )


  {


    // ...


  }


}


Did you get the point on first reading?[1]

[1] Checking for self-assignment in a constructor doesn't make sense, because the other object can't be the same as our own object梠ur own object is still in the process of being created! When I first wrote that code joke, I thought (correctly) that the test should be meaningless. Still, it turns out that this one actually falls into the category of "protecting against Machiavelli." Friend and astute reader Jim Hyslop pointed out that there is a piece of (technically illegal) code using placement new that, if it were legal, would make the test meaningful:






T t; 


new (&t) T(t);  // illegal, but it would make the test meaningful





Yikes! I'm not sure whether to be happy that people appreciated my code joke, or to be deeply worried that they immediately started trying to find subversive ways to make it meaningful.

Similarly, the following code is also not valid C++, in that it's syntactically legal (a conforming compiler must accept it) but has undefined behavior (a conforming compiler may legitimately emit code that will reformat your hard drive). If the code were valid, it would also make the test meaningful:






T t = t; // invalid, but it would make the test meaningful 





Postscript #2

Note that there are other cases in which pointer comparison is not what most people would consider intuitive. Here are a couple of examples.

  • Comparing pointers into string literals is undefined. The reason is that the standard explicitly allows compilers to store string literals in overlapping areas of memory as a space optimization. For this reason, it's entirely possible to take a pointer into two different string literals and have them compare equal.

  • In general, you cannot compare arbitrary bald pointers using the builtin operators <, <=, >, and >= with well-defined results, although the results are defined in specific situations (for example, pointers to objects in the same array can be so compared). The standard library works around this limitation by saying that the library's comparison function templates, less<> and its brothers, must give an ordering of pointers. This is necessary so that you can create, say, a map with keys of pointer type梖or example, a map<T*,U> that, with the default template parameter, is a map<T*,U,less<T*> >.

    I l@ve RuBoard Previous Section Next Section