|
|
|
Advertisement
|
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.
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.
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]
|
Sponsored Links
|