跳到内容

更多 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
华夏公益教科书