Friday, August 12, 2011

Safe bool idiom

Note: This is just a rewrite of The Safe Bool Idiom by Bjorn Karlsson in my own words.

How do you provide for boolean tests for your classes in C++?

It is trivial for raw pointers:

if (T* p = get_some_value()){

// p is valid and not null we can use it here
}
else{
// p is null/invalid.
}
Any rvalue of arithmetic, enumeration, pointer, or pointer to member type, can be implicitly converted to an rvalue of type bool.

Smart pointers

May be you have a smart pointer class:

typedef smart_ptr<T> TPtr;
TPtr p(get_some_value());
You might have a member function for testing in boolean contexts:
if(p.is_valid()){

// p is valid, use it
}
else{
// p is not valid. Take proper action.
}

Cons

  • Verbose
  • p must be declared outside the if block (the scope in which it is really used).
  • The name is_valid may be different for different smart ptr like classes.

Objective

  • It should be possible to convert existing code to make use of smart pointers from raw pointers with minimum change in code base.

The obvious solution (operator bool)

We will use a class Testable in following.

May be you would code something like this:

class Testable {

bool ok_;
public:
explicit Testable(bool b=true):ok_(b) {}

operator bool() const {
return ok_;
}
};

We can use like following:

Testable test;

if (test)
std::cout << "Yes, test is working!\n";
else
std::cout << "No, test is not working!\n";

Bravo!

But what about:

test << 1;

int i=test;
  • A bool operator introduces nonsense operations which are legal and allowed by C++ compiler.
  • All thanx to implicit conversion.

Take more side effects:

Testable a;

AnotherTestable b;

if (a==b) {
}

if (a<b) {
}

A possible enhancement here:

class Testable {

bool ok_;
private int () const ;
public:
explicit Testable(bool b=true):ok_(b) {}

operator bool() const {
return ok_;
}
};

Next comes operator !

Remember the old C trick?

int x = !! y; // maps y to 0 or 1.

We can do something like this in C++:

class Testable {

bool ok_;
public:
explicit Testable(bool b=true):ok_(b) {}

bool operator!() const {
return !ok_;
}
};

Now we can write:

Testable test;

if (!!test)
std::cout << "Yes, test is working!\n";
if (!test2) {
std::cout << "No, test2 is not working!\n";

Pros:

  • No more implicit conversions
  • No more overloading issues
  • Known as double-bang trick

Cons:

  • Not as straight-forward as if(test)

The innocent void*

We can off course write a conversion function to void*:

class Testable {

bool ok_;
public:
explicit Testable(bool b=true):ok_(b) {}

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

This can be safely used in if(test).

But what about following?

Testable test;

delete test;
  • The idiom is flawed
  • It is possible to compare objects of different types as all have implicit conversion to void*.

Lets go for a nested class

Courtesy Don Box 1996, C++ Report:

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;
}
};

Pros

  • Rather than using a void* we are using a nested class.
  • It won’t be possible to compare objects of different types.
  • There is no need to define the nested class.

Cons:

Testable b1,b2;


if (b1==b2) {
}

if (b1<b2) {
}

The safe bool idiom

Lets declare an unspecified nested function type:

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;
}
};
  • Testable::*bool_type is typedef for a pointer to a const member function of Testable.
  • this_type_does_not_support_comparisons is a private function with same signature as bool_type.
  • We introduce a conversion operator to bool_type. We return 0 if Testable is invalid. We return pointer tothis_type_does_not_support_comparisons if Testable is valid.

The above code still allows following:

Testable test;

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

Lets close the loop as follows:

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;
}
  • this_type_does_not_support_comparisons is private
  • operator== and operator!= are now non-members
  • Any attemp to call them will result in compiler error.
  • The error will be generated only if there is an attempt to instantiate these templates. So no extra code is going to be added in the executable.
Assignments
  • Write a base class which can implement this idiom as a reusable component.
  • Study the implementation of boost::scoped_ptr especially boost/smart_ptr/detail/operator_bool.hpp.
References

No comments: