是否有任何库可以帮助在 C++ 应用程序中实现按合同原则设计的设计?
特别是,我正在寻找一个可以使用该原理的库,就像这样。
我遵循以下文章的教义:
我最终应用的几乎是 Samek 的方法。只需为 REQUIRE、ENSURE、CHECK 和 INVARIANT(基于现有assert
宏)创建宏就非常有用。当然,它不如本地语言支持好,但无论如何,它可以让您从该技术中获得大部分实用价值。
至于库,我不认为使用一个是值得的,因为断言机制的一个重要价值是它的简单性。
有关调试代码和生产代码之间的区别,请参阅断言何时应保留在生产代码中?.
最简单的?
在函数开始时断言语句以测试您的要求。在函数末尾断言语句以测试结果。
是的,它很粗糙,它不是一个大系统,但它的简单性使它具有通用性和便携性。
一些设计模式,例如非虚拟接口,可以很自然地为给定方法编写前置/后置条件:
#include <cassert>
class Car {
virtual bool engine_running_impl() = 0;
virtual void stop_impl() = 0;
virtual void start_impl() = 0;
public:
bool engine_running() {
return engine_running_impl();
}
void stop() {
assert(engine_running());
stop_impl();
assert(! engine_running());
}
void start()
{
assert(! engine_running());
start_impl();
assert(engine_running());
}
}
class CarImpl : public Car {
bool engine_running_impl() {
/* ... */
}
void stop_impl() {
/* ... */
}
void start_impl() {
/* ... */
}
}
试试这个:Contract++。它已被 Boost 接受(但尚未发货)。
我有一个带有要求、保险和不变量的小 c++ 标头。它的位置少于 400,应该可以满足您的需求。你可以在dhc.hpp下找到它,它以一种有用的方式报告错误,并且可以通过定义编译出来。
#include <dbc.hpp>
class InvarTest {
public:
int a = 0;
int b = 9;
INVARIANT_BEGIN
Inv(RN(0,a,32));
Inv(RN(0,b,10));
INVARIANT_END
inline void changeMethod() {
Invariant(); // this runs the invariant block at the beginning and end of the method
a = 33;
}
};
int testFunc(int a, double d, int* ip) {
// RN = a in range 0 to 10, NaN = not a number, NN = not null
Rqr(RN(0,a,10), NaN(d), RN(0.0,d,1.0), NN(ip));
// Enr return the passed value
return Esr(RN(0.0,a+d,20.3));
}
void testFunc2(std::vector<int>& a, std::shared_ptr<int> sp) {
Rqr( SB(a,0), TE(a.size() % 12 == 0), NN(sp));
}
如果您不介意使用C++0x 功能,您可以使用lambdas和 RAII 实现前置条件和后置条件。
后置条件的一个简单示例:
struct __call_on_destructor {
std::tr1::function<void()> _function;
template<class Func> inline __call_on_destructor(Func func) {
_function = func;
}
inline ~__call_on_destructor() {
_function();
}
};
#define on_scope_exit(function) \
__call_on_destructor PP_UNIQUE_LABEL(on_exit) (function)
#define ensures(expression) \
on_scope_exit([&] () { assert(expression); })
同样,您可以实现先决条件和不变量。代码取自一个极其简单的C++0x 合同库。
使用标准 ASSERT/Q_ASSERT,但要注意“无效”断言,特别是如果您将此类诊断留在外部测试中(在没有 NDEBUG 的情况下构建)。
关于 C++ 项目中的 DBC 实现(使用断言)和“始终启用调试”策略的小故事。
我们使用非常标准的工具(ASSERT()/Q_ASSERT())作为 DBC 实现,直到我们在集成测试中遇到以下情况:我们的最新构建总是在 start 之后失败。发布这样的版本不是很专业(经过一周的内部 QA 工作)。
问题是如何引入的?
结果,糟糕的开发人员被指责为这个错误(显然没有这个 ASSERT 就不会崩溃),我们不得不发布修补程序以允许继续进行集成测试。
首先:我需要在集成测试中启用断言来跟踪失败的条件(断言越多越好),另一方面,我不希望开发人员担心一些“额外的”断言会导致整个软件堆栈崩溃。
我发现,对于这个问题,基于 C++ 的解决方案可能很有趣:弱断言。这个想法不是在断言失败时停止整个应用程序,而是记录堆栈跟踪以供以后分析并继续。我们可以根据需要检查尽可能多的期望,而不必担心崩溃,并且我们会从集成中获得反馈(堆栈跟踪)。单个进程运行可以提供许多失败的断言案例进行分析,而不仅仅是一个(因为没有调用 abort())。
这里简要描述了这个想法的实现(使用一些 LD_PRELOAD 魔法):http: //blog.aplikacja.info/2011/10/assert-to-abort-or-not-to-abort-thats-the-question/