图书馆方便使用“按合同设计”原则

时间:2009-07-24 06:58:57

标签: c++ design-by-contract

是否有任何库有助于在C ++应用程序中按合同原则实现设计?

特别是,我正在寻找一个能够使用该原则的库,例如this

7 个答案:

答案 0 :(得分:8)

我遵循以下文章的教导:

  • An exception or a bug?(米罗 Samek,C / C ++ Users Journal,2003)
  • 简单支持合同设计 用C ++ (Pedro Guerreiro,TOOLS,2001)

我最终应用的是Samek的方法。只需为REQUIRE,ENSURE,CHECK和INVARIANT创建宏(基于现有的assert宏)非常有用。当然它不如母语支持那么好,但无论如何,它可以让你从这项技术中获得大部分实用价值。

至于图书馆,我认为使用图书馆并不值得,因为断言机制的一个重要价值就是它的简单性。

有关调试代码和生产代码之间的区别,请参阅When should assertions stay in production code?

答案 1 :(得分:6)

最简单?

在函数开头声明语句以测试您的要求。 在函数末尾断言以测试结果。

是的,它很粗糙,它不是一个大系统,但它的简单性使它具有多功能性和便携性。

答案 2 :(得分:6)

某些设计模式(例如non-virtual interface)可以自然地为给定方法编写前/后条件:

#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() {
        /* ... */
    }
}

答案 3 :(得分:2)

如果您不介意使用C++0x features,则可以使用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 Contracts library

答案 4 :(得分:2)

试试这个:Contract++。它已被Boost接受(但尚未发货)。

答案 5 :(得分:2)

我有一个带有需求,保险和不变量的litle c ++标头。它不到400 loc,应该满足您的需求。您可以在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));
}

答案 6 :(得分:1)

使用标准ASSERT / Q_ASSERT ,但要注意“无效”断言,尤其是在外部测试中保留此类诊断时(不使用NDEBUG构建)。

关于C ++项目中DBC实现(使用断言)和“调试始终启用”策略的小故事。

我们使用非常标准的工具(ASSERT()/ Q_ASSERT())作为DBC实现,直到我们在集成测试中遇到以下情况:我们最新的构建在启动后总是失败。发布此类版本并不是非常专业(在内部质量保证工作一周之后)。

问题是如何引入的?

  • 开发人员在源代码中错误断言(逻辑表达式无效)
  • 我们所有的预发布版本都启用了断言(用于跟踪集成测试中的错误)
  • 内部质量检查与集成测试具有不同的环境设置,因此“断言错误”不可见

结果是穷人开发人员被指责为这个错误(显然没有这个ASSERT就没有崩溃)我们不得不发布修补程序以允许继续进行集成测试。

首先:我需要在集成测试中使用断言来跟踪失败的条件(断言越多越好),另一方面我不希望开发人员害怕< / strong>某些“额外”ASSERT会使整个软件堆崩溃。

我发现,这个问题很可能是基于C ++的有趣解决方案:弱的asserions。这个想法是不能停止整个应用程序失败的断言,但记录堆栈跟踪以供以后分析并继续。我们可以检查尽可能多的期望而不用担心崩溃,我们从集成中获得反馈(堆栈跟踪)。单个进程运行可以提供许多失败的断言案例进行分析,而不是只有一个(因为没有调用abort())。

这个想法的实现(使用一些LD_PRELOAD魔术)在这里简要描述:http://blog.aplikacja.info/2011/10/assert-to-abort-or-not-to-abort-thats-the-question/