更多 C++ 惯用法/安全布尔值
外观
为类提供布尔测试,但限制它不参与不需要的表达式。
用户提供的布尔值转换函数可能弊大于利,因为它允许它们参与理想情况下不希望它们参与的表达式。如果定义了一个简单的转换运算符,那么两个或多个不相关类的对象可以进行比较。类型安全受到损害。例如,
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;
}
安全布尔值惯用法允许使用直观的 if 语句进行测试的语法便利性,但同时阻止无意中编译的语句。以下是安全布尔值惯用法的代码。
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_ ?
&Testable::this_type_does_not_support_comparisons : 0;
}
};
template <typename T>
bool operator!=(const Testable& lhs, const T&) {
lhs.this_type_does_not_support_comparisons();
return false;
}
template <typename T>
bool operator==(const Testable& lhs, const T&) {
lhs.this_type_does_not_support_comparisons();
return false;
}
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;
}
可重用解决方案
有两种可能的解决方案:使用带有虚拟函数的基类来实现实际逻辑,或者使用知道在派生类中调用哪个函数的基类。由于虚拟函数会带来成本(特别是如果要增强布尔测试的类不包含任何其他虚拟函数)。请参见下面的两个版本
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;
}
protected:
virtual bool boolean_test() const=0;
virtual ~safe_bool() {}
};
template <typename T>
bool operator==(const safe_bool<T>& lhs, bool b) {
return b == static_cast<bool>(lhs);
}
template <typename T>
bool operator==(bool b, const safe_bool<T>& rhs) {
return b == static_cast<bool>(rhs);
}
template <typename T, typename U>
bool 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>
bool operator!=(const safe_bool<T>& lhs,const safe_bool<U>& rhs) {
lhs.this_type_does_not_support_comparisons();
return false;
}
以下是使用 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;
}
在 C++ 中,不能在派生类中获取受保护成员函数的地址。派生类可以是标准类、类模板或类模板的特化。安全布尔值惯用法的某些实现将 safe_bool_base::this_type_does_not_support_comparisons 声明为受保护的,该地址不能在派生类中获取——这是可重用安全布尔值惯用法中的一个要求。
由 Krzysztof Czainski 发起的在 boost 邮件列表上的 有见地的讨论 导致使用 CRTP 的 实现 安全布尔值惯用法,以及另一个使用宏的实现。
在 C++11 标准中,提供显式转换运算符作为显式构造函数的并行。这个新特性以一种干净的方式解决了问题。[1][2][3]
struct Testable
{
explicit operator bool() const {
return false;
}
};
int main()
{
Testable a, b;
if (a) { /*do something*/ } // this is correct
if (a == b) { /*do something*/ } // compiler error
}
boost::scoped_ptr
boost::shared_ptr
boost::optional
boost::tribool