跳到內容

更多 C++ 慣用法/安全布林值

來自 Wikibooks,開放世界中的開放書籍

安全布林值

[編輯 | 編輯原始碼]

為類提供布林測試,但限制它不參與不需要的表示式。

也稱為

[編輯 | 編輯原始碼]

使用者提供的布林值轉換函式可能弊大於利,因為它允許它們參與理想情況下不希望它們參與的表示式。如果定義了一個簡單的轉換運算子,那麼兩個或多個不相關類的物件可以進行比較。型別安全受到損害。例如,

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
[編輯 | 編輯原始碼]

參考資料

[編輯 | 編輯原始碼]

http://www.artima.com/cppsource/safebool.html

  1. N2437:顯式轉換運算子草案工作檔案
  2. http://stackoverflow.com/questions/6242768/is-the-safe-bool-idiom-obsolete-in-c11
  3. http://stackoverflow.com/questions/13193766/isset-or-operator-void-or-explicit-opertor-bool-or-something-else
華夏公益教科書