4

我有两个功能:

void prepare() 和 void finish() 将按顺序调用,如:

prepare();
<do something>;
finish(); 
... 
prepare(); 
<do something>; 
finish();

我想做一个简单的断言来简单地测试它们实际上是以这种方式被调用的,并且它们没有在应用程序中被同时调用或乱序调用。

此应用程序是单线程应用程序。这是一个简单的开发/测试完整性检查,以确保这些函数被按顺序调用,并且无论出于何种原因,它们不会被同时调用。此外,这些断言/完整性检查应该从生产代码中省略,因为性能至关重要!

像这样的简单 assert() 效果最好吗?

int test = 0;

void prepare() {
   assert(++test == 1);

   .
   .
   .
}

void finish() {
    assert(--test == 0);

    .
    .
    .
}
4

6 回答 6

3

这里有一个竞争条件:两个并发的实例prepare可能同时取值test,然后都在寄存器中递增它以获取1,然后进行比较以获取true

制造它volatile不会有帮助。相反,您应该在 上放置一个互斥锁test,如下所示:

boost::mutex mtx;
int test = 0;

void prepare()
{
    boost::mutex::scoped_try_lock lock(&mtx);
    assert(lock.owns_lock());
    assert(test++ == 0);
    // ...
}

void finish()
{
    boost::mutex::scoped_try_lock lock(&mtx);
    assert(lock.owns_lock());
    assert(--test == 0);
}
于 2011-02-01T15:49:46.747 回答
3

你可能想改变

int test = 0;

#ifndef NDEBUG
int test = 0;
#endif

以满足您的要求,即“应从生产中省略与此测试相关的任何代码”。

于 2011-02-01T15:51:38.417 回答
3

你可能想要:

int test = 0;

void prepare() {
    // enter critical section
    assert(test++ == 0);

    .
    .
    .
    // leave critical section 
}

void finish() {
    // enter critical section
    assert(--test == 0);

    .
    .
    .
    // leave critical section
}
于 2011-02-01T15:52:12.653 回答
1

您的代码没问题,除非您需要允许嵌套preparefinish调用。

如果不允许嵌套,您可以使用 abool而不是 a int

bool locked = false;;

void prepare() {
    assert( ! locked );
    locked = true;
    ...
}

void finish() {
    assert( locked );
    locked = false;
    ...
}
于 2011-02-01T15:51:18.117 回答
1

既然您使用的是 C++,为什么不使用 RAII?您仍然需要检查重入使用,但 RAII 大大简化了事情。结合larsmans 的 mutexRaedwald 在 NDEBUG 中的消除

struct Frobber {
  Frobber() {
    assert(mtx.try_lock());
#ifndef NDEBUG
    try {  // in case prepare throws
#endif
      prepare();
#ifndef NDEBUG
    }
    catch (...) {
      mtx.unlock();
      throw;
    }
#endif
  }

  void something();
  // And the other actions that can be performed between preparation and finishing.

  ~Frobber() {
    finish();
#ifndef NDEBUG
    mtx.unlock();
#endif
  }

private:
#ifndef NDEBUG
  static boost::mutex mtx;
#endif

  Frobber(Frobber const&);  // not defined; 0x: = delete
  Frobber& operator=(Frobber const&);  // not defined; 0x: = delete
};
#ifndef NDEBUG
boost::mutex Frobber::mtx;
#endif

void example() {
  Frobber blah;  // instead of prepare()
  blah.something();
  // implicit finish()
}

在里面的例子中,如果没有先准备,你根本无法做某事,即使抛出异常,完成总是会发生。

关于 NDEBUG 的旁注:如果您以这种方式使用它,请确保它在所有翻译单元中始终定义或始终未定义,而不是它用于断言的方式(允许在各个点定义和未定义)。

于 2011-02-01T16:05:07.070 回答
1

如果您<do something>;输入 a class,则可以完全减少检查的需要:

只需调用构造函数prepare和析构函数即可finish。然后它会自动强制它们被适当地调用。

请注意,并发和嵌套问题仍然存在:如果您想防止嵌套,那么您仍然需要某种全局状态(静态类成员?)来跟踪它,并且如果它用于多个线程访问该状态计数器需要互斥保护。

另请注意,您还可以设为私有operator new/delete以防止有人在堆上创建一个而不破坏它。

于 2011-02-01T16:09:03.410 回答