|
|
|
Sponsored Link •
|
|
Advertisement
|
class safe_bool_base {
protected:
typedef void (safe_bool_base::*bool_type)() const;
void this_type_does_not_support_comparisons() const {}
safe_bool_base() {}
safe_bool_base(const safe_bool_base&) {}
safe_bool_base& operator=(const safe_bool_base&) {return *this;}
~safe_bool_base() {}
};
template <typename T=void> class safe_bool : public safe_bool_base {
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() {}
};
template<> class safe_bool<void> : public safe_bool_base {
public:
operator bool_type() const {
return boolean_test()==true ?
&safe_bool_base::this_type_does_not_support_comparisons : 0;
}
protected:
virtual bool boolean_test() const=0;
virtual ~safe_bool() {}
};
template <typename T, typename U>
void operator==(const safe_bool<T>& lhs,const safe_bool<U>& rhs) {
lhs.this_type_does_not_support_comparisons();
return false;
}
template <typename T,typename U>
void operator!=(const safe_bool<T>& lhs,const safe_bool<U>& rhs) {
lhs.this_type_does_not_support_comparisons();
return false;
}
Here's how to use safe_bool:
class Testable_with_virtual : public safe_bool<> {
protected:
bool boolean_test() const {
// Perform Boolean logic here
}
};
class Testable_without_virtual :
public safe_bool <Testable_without_virtual> {
public:
bool boolean_test() const {
// Perform Boolean logic here
}
};
The first class, Testable_with_virtual, derives publicly
from safe_bool, and implements a virtual function
boolean_test—this function is called whenever an
instance is tested (as in if (obj){}, or if
(!obj){}). The second class,
Testable_without_virtual, also derives publicly from
safe_bool, and in addition, it passes itself as a template
parameter to its base class. This little trick—known as the
Curiously Recurring Template Pattern— enables the base
class to downcast (to the derived class) using static_cast
and call boolean_test with no extra runtime overhead and no
virtual function calls. Some people may feel that this is a slight misuse
of inheritance; while it might be argued that an instance of a derived
class is-a safe_bool of sorts, this is certainly not
the intent of this code. However, there is little reason to believe that
even neophyte programmers will fall into the trap of misunderstanding this
relationship. The destructors of the safe_bool classes are
protected to minimize the potential for misuse. But there's still hope
for the (in my opinion, overly) conscientious object-oriented purist; use
private inheritance, and make the conversion function public by
reintroducing it in the correct scope:
class Testable_without_virtual :
private safe_bool <Testable_without_virtual> {
public:
using safe_bool<Testable_without_virtual>::operator safe_bool;
bool boolean_test() const {
return true; // Logic goes here!
}
};
Matthew Wilson
[5] pointed
out that the inheritance strategy (using safe_bool as a base
class) may lead to size penalties on some compilers, specifically, those
that do not implement EBO (Empty Base Optimization) properly. Although
most modern compilers do when it comes to single inheritance, there may be
a size penalty with multiple inheritance.
void* conversion in iostreams. Do
you know which state flags are considered in that test? Most people
probably think they do. I looked it up and realized that at least I was
wrong—the eofbit flag is ignored. This goes to show
that if programmers' expectations of the semantics may differ, providing a
member function with a descriptive name is much better. In the case of
iostreams, it's reasonable to test for errors using fail(),
and failure of input using !good(). For a container class,
having a conversion function would be absolutely disastrous, because the
possible interpretations of its meaning are so plentiful. For most
classes, the proper way to design tests for validity is to provide member
functions with clever names, not clever conversion functions. There you
go.
Without the work of these people, and others who've come to the same conclusions, chances are that the safe bool idiom would not have seen the light of day.
operator== and
operator!= was invented by Douglas Gregor, and the technique
is sometimes referred to as �poisoning operators�. I've added an
(parameterized) implementation that fails to compile (when instantiated)
rather than just defining the operators and waiting until link time for
the error to appear.
Discuss this article in the Articles Forum topic, The Safe Bool Idiom.
Bjorn Karlsson is proud to be a C++ designer, programmer, teacher, preacher,
and student. He has finally learned enough about C++ to realize how little
he knows. When not reading or writing articles, books, or code, he has the
privilege to be a part of the Boost community, and a member of The C++
Source Advisory Board. He appreciates it when people send him interesting
emails at bjorn.karlsson@readsoft.com.
|
Sponsored Links
|