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()){Any rvalue of arithmetic, enumeration, pointer, or pointer to member type, can be implicitly converted to an rvalue of type bool.
// p is valid and not null we can use it here
}
else{
// p is null/invalid.
}
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.
- 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.
No comments:
Post a Comment