Простое утверждение для заказанного не повторного вызова? - PullRequest
4 голосов
/ 01 февраля 2011

У меня есть две функции:

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);

    .
    .
    .
}

Ответы [ 6 ]

3 голосов
/ 01 февраля 2011

Вы, вероятно, хотите:

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
}
3 голосов
/ 01 февраля 2011

Возможно, вы захотите изменить

int test = 0;

на

#ifndef NDEBUG
int test = 0;
#endif

, чтобы удовлетворить ваше требование, что «любой код, относящийся к этому тесту, должен быть исключен из производства».

3 голосов
/ 01 февраля 2011

Здесь есть условие гонки: два одновременных экземпляра 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);
}
1 голос
/ 01 февраля 2011

Если вы положите <do something>; в class, вы можете вообще уменьшить потребность в чеке:

Просто вызовите конструктор prepare и вызов деструктора finish. Затем автоматически принудительно вызывается, что они вызваны соответствующим образом.

Обратите внимание, что проблемы параллелизма и вложения по-прежнему актуальны: если вы хотите предотвратить вложение, вам все равно потребуется какое-то глобальное состояние (член статического класса?), Чтобы отслеживать это, и если оно используется в нескольких потоках доступ к этому счетчику должен быть защищен мьютексом.

Также обратите внимание, что вы также можете сделать приватным operator new/delete, чтобы предотвратить создание кем-либо в куче и не уничтожать его.

1 голос
/ 01 февраля 2011

Поскольку вы используете C ++, почему бы не использовать RAII? Вам все еще нужно проверить на повторное использование, но RAII значительно упрощает вещи. В сочетании с мьютексом Ларсмана и устранением Рэйдвальда в 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: если вы используете его таким образом, убедитесь, что оно всегда определено или всегда неопределено в всех единицах перевода, в отличие от того, как оно используется для assert (позволяя определить и не определено в различных точках).

1 голос
/ 01 февраля 2011

Ваш код в порядке, если только вам не нужно разрешить вложение вызовов prepare и finish.

Если вложение не разрешено, вы можете использовать bool вместо int:

bool locked = false;;

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

void finish() {
    assert( locked );
    locked = false;
    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...