2

因此,我们正在研究使用范围保护或一些类似机制来确保传入/传出对象的有效性和/或内部状态不变性,类似于 C# 代码合同。

在正常处理过程中出现意外情况/异常导致某些对象处于不一致状态的特定情况下,我们可以/应该使用什么机制来回避范围保护将抱怨的事实当我们跳出函数?

这是一些示例伪代码来说明我的观点:

struct IObjectValidator;

struct ObjectValidatorScopeGuard
{
  ObjectValidatorScopeGuard(IObjectValidator * pObj) 
    : m_ptr(pObj) 
  {
    Assert(!m_ptr || m_ptr->isValid());
  }

  ~ObjectValidatorScopeGuard()
  {
    Assert(!m_ptr || m_ptr->isValid());
  }
  private:
    IObjectValidtor * m_ptr;
};


int SomeComponent::CriticalMethod(const ThingA& in, ThingB& inout, ThingC * out)
{
  ObjectValidatorScopeGuard sg1(static_cast<IObjectValidator *>(&in));   
  ObjectValidatorScopeGuard sg2(static_cast<IObjectValidator *>(&inout));
  ObjectValidatorScopeGuard sg3(static_cast<IObjectValidator *>(out));

  // create out
  try
  {
     out = new ThingC();
     out->mergeFrom(inout, out); // (1)
  }
  catch (const EverythingHasGoneHorriblyWrongException& ex)
  {
     // (2) out and inout not guaranteed valid here..
  }
  return 0;
}

因此,如果 (1) 中出现问题导致“out”或“inout”在点 (2) 处于错误状态,则范围保护 sg2/sg3 将抛出异常......并且这些异常可能会掩盖真正的原因。

是否有任何模式/约定可以处理这种情况?我们是否遗漏了一些明显的东西?

4

2 回答 2

6

如果对象验证器保护的代码块出现异常,C++ 运行时将调用terminate. 在处理其他异常时,您不能像析构函数那样抛出异常。因此,您不应该从析构函数中抛出异常(详情请点击此处)。您应该使用断言或记录错误,而不是抛出异常。

比检查不变量更好的是保证它们永远不会被破坏。这就是所谓的异常安全。基本的异常安全(保留不变量)通常很容易通过巧妙地重新排序语句和使用RAII来实现。

异常安全技术示例:

class String {
  char *data;

  char *copyData(char const *data) {
    size_t length = strelen(rhs->data);
    char *copy = new char[length];
    memcpy(data, rhs->data, length);
    return data;
  }

public:
  ~String() { delete[] data; }

  // Never throws
  void swap(String &rhs) { std::swap(data, rhs->data); }

  // Constructor, Copy constructor, etc.
};

// No exception safety! Unusable!
String &String::operator = (String const &rhs) {
  if(&rhs == this) return *this;

  delete[] data;
  data = copyData(rhs->data); // May throw
}

// Weak exception safety
String &String::operator = (String const &rhs) {
  if(&rhs == this) return *this;

  delete[] data;
  data = 0; // Enforce valid state
  data = copyData(rhs->data); // May throw
}

// Strong safety 1 - Copy&Swap with explicit copy
String &String::operator = (String const &rhs) {
  String copy(rhs);// This may throw
  swap(copy);// Non-throwing member swap
  return *this;
}

// Strong safety 2 - Copy&Swap with pass by value
String &String::operator = (String rhs) {
  swap(rhs);// Non-throwing member swap
  return *this;
}
于 2012-05-22T20:41:17.317 回答
2

将断言放在范围保护中很有趣。这不是通常的用例,但提高其覆盖率并不是一个坏主意。

请注意,当您已经在处理一个异常时,您不能再抛出另一个异常。因此inout, 或inout不能委托给其他地方的问题,您需要立即处理。

如果您只想在断言被违反时打印一条调试消息(预期的行为Assert),那么只需打印该消息并继续您的方式……不要搞乱异常。

如果Assert应该绑定到更大的异常处理机制,那么异常对象应该具有适应Assert实际产生的任何内容的结构。但是将该状态放入适当的异常对象中并非易事。Assert在堆栈展开期间调用,在处理异常之前,在它可以被重新抛出 ie ( try { throw; } catch ( structured_e & ) {}) 访问之前。您需要一个线程局部变量来存储当前的结构化异常,由structured_e::structured_e().

长话短说,我的建议是提供一个单独WeakAssert的用于析构函数和范围保护,它不会引发异常。

另请参阅Herb Sutter关于组合异常和析构函数时为什么不聪明的文章。

于 2012-05-23T07:06:42.903 回答