The Artima Developer Community
Sponsored Link

The C++ Source
The Safe Bool Idiom
by Bjorn Karlsson
July 31, 2004

<<  Page 2 of 3  >>

Advertisement

A Seemingly Innocent Approach � operator void*

Here's a clever idea—using a conversion function to void*. It's clever because there aren't that many things you can do with a void* except test it in Boolean contexts. Here's how it works:

  operator void*() const {
    return ok_==true ? this : 0;
  }

Another trivial implementation! Don't worry, from here on nothing's trivial... As you might have guessed, this solution is flawed, too. The problem is that it is now possible to do this:

  Testable test;
  delete test; 

Ouch! If you think that this situation can be saved with a little const trickery, think again: The C++ Standard explicitly allows delete expressions with pointers to const types [2]. Perhaps the best-known use of this technique comes from the C++ Standard� the conversion that allows the state of iostreams to be queried uses it. However, while the intention is this;

  if (std::cout) { // Is the stream ok?
  }

it is also quite possible to do this;

  std::cout << std::cin << std::cout;

Also, using a conversion like this means that it is possible to test instances of different types in Boolean contexts (all types that utilize this flawed idiom). So, it's time to get radical, and travel deeper into C++ territory.

Almost Getting There with a Nested Class

In 1996, Don Box wrote about a very clever technique in his C++ Report column�a technique originally created to support testing for nullness�that almost does what we came here for. It involves a conversion function to a nested type (that doesn't even need to be defined), like so:
  class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}

    class nested_class;

    operator const nested_class*() const {
      return ok_ ? reinterpret_cast<const nested_class*>(this) : 0;
    }
  };

Now, this version supports Boolean tests, but alas, too much so. We're now able to write erroneous things like this:

  Testable b1,b2;

  if (b1==b2) {
  }

  if (b1<b2) {
  }

We could poison all operators that have been enabled by the conversion to make the algorithm a better fit for our purposes, but there's an even better way.

The Safe Bool Idiom

It's time to make these tests safe. Remember that we need to avoid unsafe conversions that allow for erroneous usage. We must also avoid overloading issues, and we definitely shouldn't allow deletion through the conversion. So, what do we do? Without further ado, let me give you the solution in code.
  class Testable {
    bool ok_;
    typedef void (Testable::*bool_type)() const;
    void this_type_does_not_support_comparisons() const {}
  public:
    explicit Testable(bool b=true):ok_(b) {}

    operator bool_type() const {
      return ok_==true ? 
        &Testable::this_type_does_not_support_comparisons : 0;
    }
  };

Simple, eh? Let's examine what's going on here. First, we typedef bool_type to be a pointer to a const member function of Testable, taking zero arguments and returning void. This is our magic type that allows for testing in Boolean contexts, without taking part in overloading contexts. Next, we define a conversion function to bool_type, just as we did with bool and void* earlier. Finally, we return "true" using a pointer to a member function (this_type_does_not_support_comparisons), which fits the bool_type, and a null value for "false". It's now possible to safely test instances of Testable in Boolean contexts. The strange name does have a purpose; read on to find out what it is!

Compared to a conversion function to bool, we have avoided the unfortunate overloading issues, and the effects of returning an integral type (making some nonsense constructs legal). We have added the usability that was obscured by operator!, and disabled the potential delete issue with operator void*. Quite impressive! There's one additional twist that makes the solution complete, and that is to disable comparisons between distinct instances of Testable. With our current implementation, you can write code like this:

  Testable test;
  Testable test2;
  if (test1==test2) {}
  if (test!=test2) {}

Comparisons like the above are not only meaningless; they're dangerous, because they imply an equivalence relationship that can never exist between different instances of Testable. We need to find a way to disable such nonsensical comparisons.

  template <typename T> 
    bool operator!=(const Testable& lhs,const T& rhs) {
	lhs.this_type_does_not_support_comparisons();	
      return false;	
    } 
  template <typename T>
    bool operator==(const Testable& lhs,const T& rhs) {
	lhs.this_type_does_not_support_comparisons();
      return false;		
    }

Of course! By defining operator== and operator!= as non-members, and having them attempt to call a non-public member (this_type_does_not_support_comparisons) on the Testable argument will result in a compile error, thus disallowing tests that don't make sense [3]. (The obvious versions taking Testable as the second argument are omitted here for brevity.) Using parameterized implementations, we ensure that an error is only emitted if and when a comparison function is instantiated. The long (and fairly descriptive) name of the called member function will definitely be part of the compiler error message if a comparison function is instantiated, making it easy to locate and correct the error. For tests that you do want to allow, simply define the comparison operators as usual.

This, my friends, is the safe bool idiom. When people started using this idiom, it was discovered that there was an efficiency penalty on some compilers — the member function pointer caused a compiler headache resulting in slower execution when the address was fetched. Although the difference is marginal, the current practice is typically to use a member data pointer instead of a member function pointer. [4]

<<  Page 2 of 3  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us