Je travaille actuellement sur un projet et j'ai le problème suivant.
J'ai une méthode C++ que je veux travailler de deux manières différentes:
void MyFunction()
{
foo();
bar();
foobar();
}
void MyFunctionWithABonus()
{
foo();
bar();
doBonusStuff();
foobar();
}
Et je voudrais ne pas dupliquer mon code car la fonction réelle est beaucoup plus longue. Le problème est que je ne dois en aucun cas ajouter du temps d'exécution au programme lorsque MyFunction est appelée à la place de MyFunctionWithABonus. C'est pourquoi je ne peux pas simplement avoir un paramètre booléen que je vérifie avec une comparaison C++.
Mon idée aurait été d'utiliser des modèles C++ pour dupliquer virtuellement mon code, mais je ne peux pas penser à une façon de faire sans temps d'exécution supplémentaire et sans duplication du code.
Je ne suis pas un expert avec les modèles, alors il se peut que je manque quelque chose.
Est-ce que l'un de vous a une idée? Ou est-ce simplement impossible en C++ 11?
Avec template et lambda, vous pouvez faire:
template <typename F>
void common(F f)
{
foo();
bar();
f();
foobar();
}
void MyFunction()
{
common([](){});
}
void MyFunctionWithABonus()
{
common(&doBonusStuff);
}
ou bien vous pouvez simplement créer la fonction prefix
et suffix
.
void prefix()
{
foo();
bar();
}
void suffix()
{
foobar();
}
void MyFunction()
{
prefix();
suffix();
}
void MyFunctionWithABonus()
{
prefix();
doBonusStuff();
suffix();
}
Quelque chose comme ça fera très bien l'affaire:
template<bool bonus = false>
void MyFunction()
{
foo();
bar();
if (bonus) { doBonusStuff(); }
foobar();
}
Appelez-le via:
MyFunction<true>();
MyFunction<false>();
MyFunction(); // Call myFunction with the false template by default
Le modèle "moche" peut être évité en ajoutant des wrappers Nice aux fonctions:
void MyFunctionAlone() { MyFunction<false>(); }
void MyFunctionBonus() { MyFunction<true>(); }
Vous pouvez trouver quelques informations intéressantes sur cette technique là . C'est un "vieux" papier, mais la technique en elle-même reste tout à fait correcte.
Si vous avez accès à un compilateur Nice C++ 17, vous pouvez même pousser plus avant la technique, en utilisant le constexpr if , comme ceci:
template <int bonus>
auto MyFunction() {
foo();
bar();
if constexpr (bonus == 0) { doBonusStuff1(); }
else if constexpr (bonus == 1) { doBonusStuff2(); }
else if constexpr (bonus == 2) { doBonusStuff3(); }
else if constexpr (bonus == 3) { doBonusStuff4(); }
// Guarantee that this function will not compile
// if a bonus different than 0,1,2,3 is passer
else { static_assert(false);},
foorbar();
}
Étant donné certains commentaires formulés par l'OP concernant le débogage, voici une version qui appelle doBonusStuff()
pour les versions de débogage, mais pas les versions publiées (qui définissent NDEBUG
):
#if defined(NDEBUG)
#define DEBUG(x)
#else
#define DEBUG(x) x
#endif
void MyFunctionWithABonus()
{
foo();
bar();
DEBUG(doBonusStuff());
foobar();
}
Vous pouvez également utiliser la macro assert
si vous souhaitez vérifier une condition et échouer si elle est fausse (mais uniquement pour les versions de débogage; les versions de version n'effectuent pas la vérification).
Faites attention si doBonusStuff()
a des effets secondaires, ceux-ci ne seront pas présents dans les versions de version et pourraient invalider les hypothèses formulées dans le code.
Voici une légère variation de la réponse de Jarod42 à l'aide de modèles variadiques afin que l'appelant puisse fournir des fonctions bonus zéro ou un:
void callBonus() {}
template<typename F>
void callBonus(F&& f) { f(); }
template <typename ...F>
void MyFunction(F&&... f)
{
foo();
bar();
callBonus(std::forward<F>(f)...);
foobar();
}
Code d'appel:
MyFunction();
MyFunction(&doBonusStuff);
Une autre version, utilisant uniquement des modèles et aucune fonction de redirection, puisque vous avez indiqué que vous ne souhaitiez aucune surcharge d'exécution. En ce qui me concerne, cela ne fait qu'augmenter le temps de compilation:
#include <iostream>
using namespace std;
void foo() { cout << "foo\n"; };
void bar() { cout << "bar\n"; };
void bak() { cout << "bak\n"; };
template <bool = false>
void bonus() {};
template <>
void bonus<true>()
{
cout << "Doing bonus\n";
};
template <bool withBonus = false>
void MyFunc()
{
foo();
bar();
bonus<withBonus>();
bak();
}
int main(int argc, const char* argv[])
{
MyFunc();
cout << "\n";
MyFunc<true>();
}
output:
foo
bar
bak
foo
bar
Doing bonus
bak
Il n’existe plus qu’une version de MyFunc()
avec le paramètre bool
en tant qu’argument de modèle.
Vous pouvez utiliser la distribution de balises et une surcharge de fonctions simple:
struct Tag_EnableBonus {};
struct Tag_DisableBonus {};
void doBonusStuff(Tag_DisableBonus) {}
void doBonusStuff(Tag_EnableBonus)
{
//Do bonus stuff here
}
template<class Tag> MyFunction(Tag bonus_tag)
{
foo();
bar();
doBonusStuff(bonus_tag);
foobar();
}
Ceci est facile à lire/à comprendre, peut être étendu sans sueur (et pas de clauses sur les clauses habituelles if
- en ajoutant plus de balises), et bien sûr ne laissera aucune empreinte au moment de l'exécution.
La syntaxe d’appel est assez conviviale, mais elle peut bien sûr être encapsulée dans des appels à la vanille:
void MyFunctionAlone() { MyFunction(Tag_DisableBonus{}); }
void MyFunctionBonus() { MyFunction(Tag_EnableBonus{}); }
La répartition des tags est une technique de programmation générique largement utilisée, here est un article de Nice sur les bases.