更多 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_ptrboost::shared_ptrboost::optionalboost::tribool