assert simple pour commander non appel rentrante?
-
28-10-2019 - |
Question
J'ai deux fonctions:
vide préparer () et la finition void () qui sera appelée séquentiellement comme:
prepare();
<do something>;
finish();
...
prepare();
<do something>;
finish();
Je veux faire une simple affirmation simplement vérifier qu'ils sont en fait appelé de cette façon et qu'ils ne sont pas appelés en même temps ou hors de l'ordre dans l'application.
Cette application est une application mono-thread. C'est un simple développement / test test de cohérence pour vous assurer que ces fonctions sont appelées en ordre et que, pour une raison quelconque, ils ne sont pas appelés simultanément. En outre, ces affirmations / vérifications de bonne santé devraient être omis à partir du code de production des performances est cruciale!
serait un assert simple () comme ce travail mieux?
int test = 0;
void prepare() {
assert(++test == 1);
.
.
.
}
void finish() {
assert(--test == 0);
.
.
.
}
La solution
Votre code est OK, sauf si vous devez autoriser les appels prepare
et finish
nidification.
Si la nidification n'est pas autorisé, vous pouvez utiliser un bool
au lieu d'un int
:
bool locked = false;;
void prepare() {
assert( ! locked );
locked = true;
...
}
void finish() {
assert( locked );
locked = false;
...
}
Autres conseils
Vous pouvez modifier
int test = 0;
à
#ifndef NDEBUG
int test = 0;
#endif
pour satisfaire votre exigence que « tout code relatif à ce test doit être omis de la production ».
vous voulez probablement:
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
}
Il y a une condition de course ici. Deux instances simultanées de prepare
peut prendre la valeur de test
en même temps, alors à la fois incrément dans un registre à la fois obtenir 1
, puis faire la comparaison pour obtenir true
Rendre volatile
est pas va aider . , Vous devriez plutôt mettre un mutex sur test
, comme suit:
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);
}
Si vous mettez <do something>;
dans un class
vous pouvez réduire la nécessité d'un contrôle du tout:
Il suffit que l'appel constructeur prepare
et le finish
d'appel destructor. Ensuite, il est appliqué automatiquement on les appelle de façon appropriée.
Notez que la simultanéité et les questions de nidification appliquent encore: Si vous voulez éviter l'imbrication alors vous auriez encore besoin d'une sorte d'état global (? Membre de classe statique) de garder une trace de cela, et si elle est utilisée dans plus d'un fil l'accès à ce compteur devra être protégé mutex.
Notez également que vous pouvez aussi faire operator new/delete
privé pour empêcher quelqu'un de créer un sur le tas et non le détruire.
Puisque vous utilisez C ++, pourquoi ne pas utiliser RAII? Vous auriez encore besoin de vérifier pour rentrante, mais RAII de considérablement les choses. Combiné avec larsmans' mutex et élimination de Raedwald dans 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()
}
exemple à l'intérieur, il vous suffit ne peuvent pas faire quelque chose sans avoir d'abord la préparation et la finition sera toujours le cas, même si une exception est levée.
Note Side à propos NDEBUG: si vous utilisez cette façon, assurez-vous qu'il est soit toujours définie ou toujours pas défini dans tous unités de traduction, par opposition à la façon dont il est utilisé pour assert (lui permettant d'être définis et non définis à différents points).