使用作用域防护作为代码协定
Using scope guards as code contracts
因此,我们正在研究使用作用域防护或一些类似的机制来确保传入/传出对象的有效性和/或内部状态不变性,类似于 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 将引发异常......这些例外可能会掩盖真正的原因。
是否有任何模式/约定可用于此方案?我们错过了一些明显的东西吗?
如果对象验证器保护的代码块出现异常,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;
}
作用域防护中放置断言很有趣。这不是通常的用例,但提高其覆盖范围并不是一个坏主意。
请注意,当您已经在处理一个异常时,您不能抛出另一个异常。因此,in
、out
或inout
的问题不能委派给其他地方,您需要立即处理它。
如果您只想在违反断言时打印调试消息(Assert
的预期行为(,那么只需打印消息并继续前进......根本不搞砸例外。
如果Assert
应该绑定到更大的异常处理机制中,那么异常对象应该具有结构来容纳实际产生的任何Assert
。但是,将该状态放入适当的异常对象中并非易事。 Assert
在堆栈展开期间、异常处理之前、通过重新抛掷访问之前(即 try { throw; } catch ( structured_e & ) {}
(调用。您需要一个线程局部变量来存储当前结构化异常,由 structured_e::structured_e()
初始化。
长话短说,我的建议是提供一个单独的WeakAssert
用于析构函数和范围防护,这不会引发异常。
另请参阅 Herb Sutter 关于为什么在组合异常和析构函数时不聪明的文章。
- 未在作用域中声明unordered_map
- 有没有一种方法可以在编译时获得作用域类名
- C++quit()函数中可能存在作用域问题
- 未在此作用域OpenCV3.4中声明cvSaveImage
- 全局作用域中函数指针的赋值
- 在类函数中初始化外部作用域变量
- 不同作用域中的静态变量和全局变量
- 是同一作用域的函数部分中的函数调用
- 未在此作用域中声明的函数和变量 (C++)
- 类作用域的类型别名"using":[何时]方法中的用法可以先于类型别名?
- 将作用域枚举转换为基础类型
- 表达式必须具有完整或无作用域的枚举图
- 在构造函数中输入对象时C++类成员作用域
- 无法让"std::enable_if"适用于无作用域枚举
- 为什么不能直接引用作用域枚举类成员,而不能为无作用域枚举生成类成员?
- 大括号的作用域是否用于注释目的,从而降低C++代码的速度
- PJSIP示例代码app_perror未在作用域中声明
- 使用作用域防护作为代码协定
- Stoi未在作用域中声明-代码::块
- 模板作用域问题代码无法编译