Actualmente estoy trabajando en un proyecto y tengo el siguiente problema.
Tengo un método C ++ que quiero trabajar de dos formas diferentes:
void MyFunction()
{
foo();
bar();
foobar();
}
void MyFunctionWithABonus()
{
foo();
bar();
doBonusStuff();
foobar();
}
Y me gustaría no duplicar mi código porque la función real es mucho más larga. El problema es que no debo bajo ninguna circunstancia agregar tiempo de ejecución al programa cuando se llama a MyFunction en lugar de MyFunctionWithABonus. Es por eso que no puedo tener un parámetro booleano que verifico con una comparación de C ++.
Mi idea habría sido usar plantillas C ++ para duplicar virtualmente mi código, pero no puedo pensar en una forma de hacerlo en la que no tenga tiempo de ejecución adicional y no tenga que duplicar el código.
No soy un experto en plantillas, por lo que es posible que me falte algo.
¿Alguno de vosotros tiene una idea? ¿O es eso simplemente imposible en C ++ 11?
Respuestas:
Con template y lambda, puede hacer:
template <typename F> void common(F f) { foo(); bar(); f(); foobar(); } void MyFunction() { common([](){}); } void MyFunctionWithABonus() { common(&doBonusStuff); }
o simplemente puede crear
prefix
ysuffix
funcionar.void prefix() { foo(); bar(); } void suffix() { foobar(); } void MyFunction() { prefix(); suffix(); } void MyFunctionWithABonus() { prefix(); doBonusStuff(); suffix(); }
fuente
Algo así funcionará bien:
template<bool bonus = false> void MyFunction() { foo(); bar(); if (bonus) { doBonusStuff(); } foobar(); }
Llámalo a través de:
MyFunction<true>(); MyFunction<false>(); MyFunction(); // Call myFunction with the false template by default
La plantilla "fea" se puede evitar agregando algunos envoltorios agradables a las funciones:
void MyFunctionAlone() { MyFunction<false>(); } void MyFunctionBonus() { MyFunction<true>(); }
Allí puede encontrar buena información sobre esa técnica . Es un artículo "antiguo", pero la técnica en sí misma sigue siendo totalmente correcta.
Siempre que tenga acceso a un buen compilador de C ++ 17, incluso puede impulsar más la técnica, utilizando el constexpr si , así:
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(); }
fuente
if constexpr (bonus) { doBonusStuff(); }
.doBonusStuff()
ni siquiera se puede compilar por alguna razón en el caso de no bonificación, hará una gran diferencia.Dados algunos de los comentarios que el OP ha hecho con respecto a la depuración, aquí hay una versión que pide
doBonusStuff()
compilaciones de depuración, pero no compilaciones de lanzamiento (que definenNDEBUG
):#if defined(NDEBUG) #define DEBUG(x) #else #define DEBUG(x) x #endif void MyFunctionWithABonus() { foo(); bar(); DEBUG(doBonusStuff()); foobar(); }
También puede usar la
assert
macro si desea verificar una condición y fallar si es falsa (pero solo para compilaciones de depuración; las compilaciones de lanzamiento no realizarán la verificación).Tenga cuidado si
doBonusStuff()
tiene efectos secundarios, ya que estos efectos secundarios no estarán presentes en las versiones de la versión y pueden invalidar las suposiciones hechas en el código.fuente
#if defined(NDEBUG)
directamente es probablemente más fácil.Aquí hay una ligera variación en la respuesta de Jarod42 usando plantillas variadas para que la persona que llama pueda proporcionar funciones de bonificación cero o una:
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(); }
Código de llamada:
fuente
Otra versión, usando solo plantillas y sin funciones de redireccionamiento, ya que dijiste que no querías ninguna sobrecarga de tiempo de ejecución. Por lo que a mí respecta, esto solo aumenta el tiempo de compilación:
#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
Ahora solo hay una versión de
MyFunc()
con elbool
parámetro como argumento de plantilla.fuente
bonus<false>()
invoca la versión predeterminada de labonus
plantilla (líneas 9 y 10 del ejemplo), por lo que no hay una llamada a la función. Para decirlo de otra manera,MyFunc()
compila en un bloque de código (sin condicionales en él) yMyFunc<true>()
compila en un bloque de código diferente (sin condicionales en él).Puede utilizar el envío de etiquetas y la sobrecarga de funciones simples:
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(); }
Esto es fácil de leer / entender, se puede expandir sin problemas (y sin
if
cláusulas repetitivas , agregando más etiquetas) y, por supuesto, no dejará huella en el tiempo de ejecución.La sintaxis de llamada es bastante amigable, pero, por supuesto, se puede envolver en llamadas vanilla:
void MyFunctionAlone() { MyFunction(Tag_DisableBonus{}); } void MyFunctionBonus() { MyFunction(Tag_EnableBonus{}); }
El envío de etiquetas es una técnica de programación genérica ampliamente utilizada, aquí hay una buena publicación sobre los conceptos básicos.
fuente