是否有任何已建立的模式用于检查C ++中的类不变性?

理想情况下,将在每个公共成员功能的开头和结束时自动检查不变性。据我所知,C提供了特殊的课程 beforeafter 会员功能,但不幸的是,当时划分的设计并不是很受欢迎,除了比贾恩(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:我想没有办法声明未命名的本地变量? :)

我们仍然必须打电话 check_invariants() 在结束时手动 Foo 构造函数和开始 Foo 驱动器。但是,许多构造函数的身体和破坏者的身体都是空的。在这种情况下,我们可以使用 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:通过 thisinvariants_checker 立即调用的构造函数 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: 这种方法并不是什么新鲜事物,一个简单的Google查询,例如“检查不变性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:通过 thisinvariants_checker 立即调用的构造函数 check_invariants 通过该指针,即使 Foo 物体仍在建设中?

我不确定它是否调用 UB 技术的。实际上,这样做肯定是安全的 - 事实上,在实践中,必须在与其他班级成员有关的特定职位上宣布的班级成员迟早会成为一个问题。

问题2:您看到这种方法还有其他问题吗?你能改善它吗?

参见#2。上一个适中的课程,加上二十多二十个开发人员的延长和漏洞的距离,我认为有机会至少以98%的速度弄乱一次。
您可以通过向数据成员添加喊叫评论来减轻这种情况。仍然。

问题3:这种方法是新的还是知名的?有更好的解决方案吗?

我没有看过这种方法,但是给出了您的描述 before()after() 我立即想到了相同的解决方案。

我认为斯特鲁斯特鲁普(Stroustrup)在多年前有一篇文章(〜15?) operator->() 返回代理。然后,这可以在其CTOR和DTOR中执行前后的表现,同时忽略了通过它调用的方法。

编辑: 我看到弗雷利希添加了 一个答案逃跑了. 。当然,除非您的班级已经需要通过这样的手柄使用,否则这是您班级用户的负担。 (IOW:它行不通。)

#0:不,但是宏观可能会更好(如果您可以的话)

#1:不,但这取决于。您不能做任何会导致身体在身体之前(您可以的,但以前)进行的任何事情。这意味着您可以存储此功能,但不能访问字段或虚拟功能。如果是虚拟的,则呼叫check_invariants()是不对的。我认为这对于大多数实施将有效,但不能保证工作。

#2:我认为这会很乏味,不值得。这是我经历不变检查的经验。我更喜欢单位测试。

#3:我已经看过。如果您要这样做,这似乎是我正确的方法。

单位测试是更好的替代方法,可导致较小的代码,并且性能更好

我清楚地看到了您的驱动器正在调用通常会抛出的函数的问题,这是C ++中的禁忌,不是吗?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top