强制使用错误返回码

强制使用错误返回码
Guy Peleg
摘要:如何强迫函数的调用者必须接受并使用函数返回的错误代码。
 
Andrei Alexandrescu在他的文章“ Three Ideas” (http://www.ddj.com/dept/cpp/184401917)中描述了一个“强制使用错误返回码”的框架,用以强迫函数的调用者必须接受并使用函数返回的错误代码。本文中将称这种代码为“强制方法(forcing methods)”。
想法是简单的,也是强有力的:定义一个类模板 ErrorCode,持有从强制方法返回的错误代码。当返回的离开作用域时,它将抛出一个异常——除非调用者已经使用了这个错误码(用类型转换操作符)。调用者也可以通过将ErrorCode转型为IgnoreError来显式地忽略错误代码。以下的代码列表1展示了组成这个框架的类,例程1则展示了如何使用它。
 
代码列表 1:
struct IgnoreError {};
 
template <class T>
class ErrorCode
{
    mutable bool read_;
    T code_;
public:
    ErrorCode(const T& code) : read_(false), code_(code) {}
    ErrorCode(const ErrorCode& rhs) : read_(rhs.read_), code_(rhs.code_)
    {
        rhs.read_ = true;
    }
    operator T() {
        // 消除异常
        this->read_ = true;
        return this->code_;
    }
    operator IgnoreError() {
        // 消除异常
        this->read_ = true;
        return IgnoreError();
    }
    ~ErrorCode() {
        if (!this->read_) {
            throw MandatoryErrorCodeException(....);
        }
    }
};
 
例程 1:
ErrorCode<int> FallableFunction(); // 函数声明
ErrorCode<int> result = FallableFunction(); // ok
if (FallableFunction ()) {...} // ok
FallableFunction(); // 抛出异常
(IgnoreError) FallableFunction();    // ok
 
但是,这个框架的问题在于异常是在 ErrorCode的析构函数中抛出。我们来看一个例子,下面的例程2看上去很正常,你没有忽略返回码,你接受并使用了它们。甚至这里还有一个特定的recover方法用于捕获所有异常。是的,几乎所有任何异常。
例程 2:
void AClass::someMethod()
{
   try {
      ErrorCode<int> ec1 = this->fallableMethod_1();
      ErrorCode<int> ec2 = this->fallableMethod_2();
      if (ec1 && ec2) {
          this->doSomething();
      }
   }
   catch (...) {
      this->recover();
   }
}
这里有一个问题:如果 this->fallableMethod_2() 抛出异常,则ec1的析构函数将被调用。但此时ec1还未被使用,所以又有一个异常被抛出(从ec1的析构函数中),这将中止这个程序(set_terminate被调用但无补于事)。这对于遵循了所有规则的你来说,无疑是一个极大的惊讶。
 
解决方法
如果你想有效地使用这个框架,你必须要对它进行一点调整。
首先,你要改变 ErrorCode必须被使用的假定。其实并没有真正的办法确保用户真的使用了错误返回码。你应该更关心的是调用者是否接受了返回码,而不是关心它们有没有使用它。毕竟,调用者可以接受返回码,然后把它赋值给某个从不使用的变量。
其次,你要确保抛出的异常不会中止程序。为此,首先要增加一个新的类 ThrowalbeErrorCode(代码列表2)。这个类从那些要强制错误码检查的方法中返回,替换前面的ErrorCode。ThrowalbeErrorCode不提供对它所持有的错误码的访问。
代码列表 2:
template <class CODE>
class ThrowableErrorCode
 
{
    template <class CODE> friend class ErrorCode;
    CODE code_;
    bool throw_;
public:
    // 构造函数 —接受错误码并准备异常
    ThrowableErrorCode(CODE i_code) : code_(i_code), throw_(true) {}
    // 显式地忽略错误码并消除异常
    operator IgnoreError() {
        this-> throw_ = false;
        return IgnoreError();
    }
    ~ThrowableErrorCode() {
        // 抛出异常,除非使用了 ErrorCode<CODE> 或IgnoreError
        if (this-> throw_) {
            throw MandatoryErrorCodeException(...);
        }
    }
};
顾名思义, ThrowableErrorCode可以抛出异常。事实上,它会在离开作用域时抛出一个异常。防止它抛出异常的唯一方法是:要么显式使用ThrowableErrorCode的IgnoreError转型操作符,要么把它赋值给一个ErrorCode对象。
新的 ErrorCode(代码列表3)不再抛出异常。它负责消除ThrowableErrorCode中的异常并提供对错误码的访问。
代码列表 3:
template <class CODE>
class ErrorCode {
    CODE code_;
public:
    // 显式构造函数,确保类的使用者知道她 /他在做什么
    explicit ErrorCode(ThrowableErrorCode<CODE>& code) : code_(code.code_) {
        // 消除 ThrowableErrorCode中的异常抛出
        code.throw_ = false;
    }
    operator CODE() {
        return this->code_;
    }
    ~ErrorCode()
    {}
};
 
使用这种方法的调用者有以下选择:
l         接受返回的 ThrowableErrorCode对象并赋给一个ErrorCode对象(假设调用者将使用它)。这是好的习惯。
l         通过使用 IgnoreError操作符显式地忽略错误返回码。这也是好的习惯。
l         忽略返回的 ThrowableErrorCode对象,这时一个临时的ThrowableErrorCode实例将在某个语句的作用域中被创建然后被销毁,这将引发一个异常但不会危害整个程序。这是坏习惯。
l         用 ThrowableErrorCode对象来接受返回码,这意味着异常并没有被消除。这表示调用愿意持有一个可能抛出异常的对象,这种情况下调用者最好能清楚他们在做什么。这也是坏习惯。
 
例程 3展示了你如何使用这个新的框架。我们重新来看看例程2中出现的问题,现在代码可以很好地工作了,因为ErrorCode ec1和ec2消除了从FallableFunction返回的ThrowableErrorCode中的异常。即使FallableFunction中抛出了异常,代码也可以如你所愿到达catch语句。
例程 3:
// 函数声明
ThrowableErrorCode<int> FallableFunction();    
 
// ok
ErrorCode<int> result = FallableFunction();
if (0 == result) {...} 
 
// 抛出异常 - 但不会中止程序
if (FallableFunction ()) {...}
 
//抛出异常 - 但不会中止程序
FallableFunction();
 
// ok – 显式忽略错误返回码
(IgnoreError) FallableFunction();
 
// 可能抛出异常 – 调用者正在冒险!
ThrowableErrorCode<int> mightThrow = FallableFunction();
 
感谢 Andrei Alexandrescu!
 
 
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值