[英]checking invariants in C++
是否有任何已建立的模式来检查C ++中的类不变量?
理想情况下,将在每个公共成员函数的开头和结尾自动检查不变量。 据我所知,带有类的C after
成员函数before
和after
提供了特殊功能,但不幸的是,合同设计当时并不普及,除了Bjarne之外没有人使用该功能,所以他删除了它。
当然,在每个公共成员函数的开头和结尾手动插入check_invariants()
调用是繁琐且容易出错的。 由于RAII是处理异常的首选武器,我提出了以下方案,将不变性检查器定义为第一个局部变量,并且不变性检查器在构造和销毁时检查不变量:
template <typename T>
class invariants_checker
{
const T* p;
public:
invariants_checker(const T* p) : p(p)
{
p->check_invariants();
}
~invariants_checker()
{
p->check_invariants();
}
};
void Foo::bar()
{
// class invariants checked by construction of _
invariants_checker<Foo> _(this);
// ... mutate the object
// class invariants checked by destruction of _
}
问题#0:我想没有办法声明一个未命名的局部变量? :)
我们仍然需要在Foo
构造函数的末尾和Foo
析构函数的开头手动调用check_invariants()
。 但是,许多构造函数体和析构函数体都是空的。 在这种情况下,我们可以使用invariants_checker
作为最后一个成员吗?
#include <string>
#include <stdexcept>
class Foo
{
std::string str;
std::string::size_type cached_length;
invariants_checker<Foo> _;
public:
Foo(const std::string& str)
: str(str), cached_length(str.length()), _(this) {}
void check_invariants() const
{
if (str.length() != cached_length)
throw std::logic_error("wrong cached length");
}
// ...
};
问题1: this
传递给invariants_checker
构造函数是否有效, this
构造check_invariants
通过该指针立即调用check_invariants
,即使Foo
对象仍在构建中?
问题2:你认为这种方法还有其他问题吗? 你能提高吗?
问题3:这种方法是新的还是众所周知的? 有更好的解决方案吗?
答案#0:您可以拥有未命名的局部变量,但是您可以放弃对对象生命周期的控制 - 而对象的整个点是因为您有一个好主意,当它超出范围时。 您可以使用
void Foo::bar()
{
invariants_checker<Foo>(this); // goes out of scope at the semicolon
new invariants_checker<Foo>(this); // the constructed object is never destructed
// ...
}
但你不想要的。
答案#1:不,我相信它无效。 由所引用的对象this
仅完全构造(并且因此开始存在)时的构造完成。 你在这里玩一个危险的游戏。
回答#2&#3:这种方法并不新鲜,一个简单的谷歌查询,例如“检查不变量C ++模板”将在这个主题上产生大量的点击。 特别是,如果您不介意重载->
运算符,可以进一步改进此解决方案,如下所示:
template <typename T>
class invariants_checker {
public:
class ProxyObject {
public:
ProxyObject(T* x) : m(x) { m->check_invariants(); }
~ProxyObject() { m->check_invariants(); }
T* operator->() { return m; }
const T* operator->() const { return m; }
private:
T* m;
};
invariants_checker(T* x) : m(x) { }
ProxyObject operator->() { return m; }
const ProxyObject operator->() const { return m; }
private:
T* m;
};
这个想法是,在成员函数调用的持续时间内,您创建一个匿名代理对象,该对象在其构造函数和析构函数中执行检查。 你可以像这样使用上面的模板:
void f() {
Foo f;
invariants_checker<Foo> g( &f );
g->bar(); // this constructs and destructs the ProxyObject, which does the checking
}
理想情况下,将在每个公共成员函数的开头和结尾自动检查不变量
我觉得这太过分了; 我明智地检查不变量。 您的类的数据成员是private
(对吗?),因此只有其成员函数才能更改数据成员,从而使不变量无效。 因此,您可以在更改为参与该不变量的数据成员后立即检查不变量。
问题#0:我想没有办法声明一个未命名的局部变量? :)
你通常可以使用宏和__LINE__
掀起一些东西,但是如果你只选择一个足够奇怪的名字,它应该已经可以了,因为你不应该在同一范围内有多个(直接)。 这个
class invariants_checker {};
template<class T>
class invariants_checker_impl : public invariants_checker {
public:
invariants_checker_impl(T* that) : that_(that) {that_->check_invariants();}
~invariants_checker_impl() {that_->check_invariants();}
private:
T* that_;
};
template<class T>
inline invariants_checker_impl<T> get_invariant_checker(T* that)
{return invariants_checker_impl<T>(that);}
#define CHECK_INVARIANTS const invariants_checker&
my_fancy_invariants_checker_object_ = get_invariant_checker(this)
适合我。
问题1:
this
传递给invariants_checker
构造函数是否有效,this
构造check_invariants
通过该指针立即调用check_invariants
,即使Foo
对象仍在构建中?
我不确定它是否会调用UB技术。 在实践中,这样做肯定是安全的 - 事实上,在实践中,必须在与其他集体成员相关的特定位置宣布的集体成员迟早会成为问题。
问题2:你认为这种方法还有其他问题吗? 你能提高吗?
见#2。 选择一个中等规模的课程,由二十几个开发人员增加五年的扩展和错误修复,我认为有机会至少在98%左右搞乱一次。
您可以通过向数据成员添加喊叫注释来缓解此问题。 仍然。
问题3:这种方法是新的还是众所周知的? 有更好的解决方案吗?
我没有看到这种方法,但鉴于你对before()
和after()
描述,我立刻想到了同样的解决方案。
我认为Stroustrup有很多文章(大约15年),在那里他描述了一个句柄类重载operator->()
来返回一个代理。 然后,它可以在其ctor和dtor中执行之前和之后的操作,同时忽略通过它调用的方法。
编辑:我看到Frerich已经添加了一个充实的答案 。 当然,除非您的课程已经需要通过这样的句柄使用,否则这对您的班级用户来说是一种负担。 (IOW:它不起作用。)
#0:不,但宏观上的情况会好一些(如果你还好的话)
#1:不,但这取决于。 你不能做任何会导致它在身体之前被解除引用的东西(你可以,但就在之前,所以它可以工作)。 这意味着您可以存储它,但不能存储访问字段或虚拟功能。 如果它是虚拟的,则调用check_invariants()是不可行的。 我认为它适用于大多数实现,但不能保证工作。
#2:我认为这将是乏味的,而且不值得。 这是我使用不变检查的经验。 我更喜欢单元测试。
#3:我见过它。 如果你要这样做,对我来说似乎是正确的方法。
单元测试是更好的替代方案,可以使代码更小,性能更好
我清楚地看到你的析构函数正在调用一个经常抛出的函数的问题,这是C ++中的禁忌不是吗?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.