More C++ Idioms/Safe bool
From Wikibooks, the open-content textbooks collection
Contents |
[edit]
Safe Bool
[edit] Intent
To provide boolean tests for a class but restricting it from taking participation in unwanted expressions.
[edit] Also Known As
[edit] Motivation
User provided boolean conversion functions can cause more harm than benefit because it allows them to participate in expressions you would not ideally want them to. If a simple conversion operator is defined then two or more objects of unrelated classes can be compared. Type safety is compromised. For example,
struct Testable { operator bool() const { return false; } }; struct AnotherTestable { operator bool() const { return true; } }; int main (void) { Testable a; AnotherTestable b; if (a == b) { /* blah blah blah*/ } if (a < 0) { /* blah blah blah*/ } // The above comparisons are accidental and are not intended but the compiler happily compiles them. return 0; }
[edit] Solution and Sample Code
Safe bool idiom allows syntactical convenience of testing using an intuitive if statement but at the same time prevents unintended statements unknowingly getting compiled in. Here is the code for the safe bool idiom.
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; } }; class AnotherTestable ... // Identical to Testable. {}; int main (void) { Testable t1; AnotherTestable t2; if (t1) {} // Works as expected if (t2 == t1) {} // Fails to compile if (t1 < 0) {} // Fails to compile return 0; }
Reusable Solution
There are two plausible solutions: Using a base class with a virtual function for the actual logic, or a base class that knows which function to call on the derived class. As virtual functions come at a cost (especially if the class you're augmenting with Boolean tests doesn't contain any other virtual functions). See both versions below:
class safe_bool_base { public: typedef void (safe_bool_base::*bool_type)() const; void this_type_does_not_support_comparisons() const {} protected: safe_bool_base() {} safe_bool_base(const safe_bool_base&) {} safe_bool_base& operator=(const safe_bool_base&) {return *this;} ~safe_bool_base() {} }; // For testability without virtual function. template <typename T=void> class safe_bool : private safe_bool_base { // private or protected inheritance is very important here as it triggers the // access control violation in main. public: operator bool_type() const { return (static_cast<const T*>(this))->boolean_test() ? &safe_bool_base::this_type_does_not_support_comparisons : 0; } protected: ~safe_bool() {} }; // For testability with a virtual function. template<> class safe_bool<void> : private safe_bool_base { // private or protected inheritance is very important here as it triggers the // access control violation in main. public: operator bool_type() const { return boolean_test() ? &safe_bool_base::this_type_does_not_support_comparisons : 0; safe_bool_base::this_type_does_not_support_comparisons(); } protected: virtual bool boolean_test() const=0; virtual ~safe_bool() {} }; template <typename T> bool operator==(const safe_bool<T>& lhs, bool b) { if (b) { if (lhs) return true; else return false; } else { if (lhs) return false; else return true; } } template <typename T> bool operator==(bool b, const safe_bool<T>& rhs) { if (b) { if (rhs) return true; else return false; } else { if (rhs) return false; else return true; } } template <typename T, typename U> void operator==(const safe_bool<T>& lhs,const safe_bool<U>& rhs) { lhs.this_type_does_not_support_comparisons(); } template <typename T,typename U> void operator!=(const safe_bool<T>& lhs,const safe_bool<U>& rhs) { lhs.this_type_does_not_support_comparisons(); }
Here's how to use safe_bool:
#include <iostream> class Testable_with_virtual : public safe_bool<> { public: virtual ~Testable_with_virtual () {} protected: virtual bool boolean_test() const { // Perform Boolean logic here return true; } }; class Testable_without_virtual : public safe_bool <Testable_without_virtual> // CRTP idiom { public: /* NOT virtual */ bool boolean_test() const { // Perform Boolean logic here return false; } }; int main (void) { Testable_with_virtual t1, t2; Testable_without_virtual p1, p2; if (t1) {} if (p1 == false) { std::cout << "p1 == false\n"; } if (p1 == p2) {} // Does not compile, as expected if (t1 != t2) {} // Does not compile, as expected return 0; }
In C++, address of protected members functions can't be taken in a derived class. Derived class could be a standard class, a class template or a specialization of a class template. Some implementations of safe bool idiom declare safe_bool_base::this_type_does_not_support_comparisons as protected, address of which can't be taken in the derived class - a requirement in reusable safe bool idiom. Unfortunately, there is a access control bug in g++ compiler that allows taking address of safe_bool_base::this_type_does_not_support_comparisons in a class template even when it is declared protected.
[edit] Solution in C++0x
The upcoming C++0x standard provides explicit conversion operators as a parallel to explicit constructors. See N2437: Explicit Conversion Operator Draft Working Paper. Explicit conversion operators solve the problem in a clean way.
[edit] Known Uses
- boost::scoped_ptr
- boost::shared_ptr
- boost::optional