Obecnie pracuję nad projektem i mam następujący problem.
Mam metodę C ++, którą chcę pracować na dwa różne sposoby:
void MyFunction()
{
foo();
bar();
foobar();
}
void MyFunctionWithABonus()
{
foo();
bar();
doBonusStuff();
foobar();
}
I nie chciałbym powielać swojego kodu, ponieważ rzeczywista funkcja jest znacznie dłuższa. Problem polega na tym, że nie mogę w żadnym wypadku dodawać czasu wykonania do programu, gdy wywoływana jest funkcja MyFunction zamiast MyFunctionWithABonus. Dlatego nie mogę po prostu mieć parametru boolowskiego, który sprawdzę za pomocą porównania C ++.
Moim pomysłem byłoby użycie szablonów C ++ do wirtualnego duplikowania mojego kodu, ale nie mogę wymyślić sposobu, w którym nie mam dodatkowego czasu na wykonanie i nie muszę duplikować kodu.
Nie jestem ekspertem od szablonów, więc być może czegoś mi brakuje.
Czy ktoś z Was ma pomysł? A może jest to po prostu niemożliwe w C ++ 11?
Odpowiedzi:
Za pomocą szablonu i lambdy możesz:
template <typename F> void common(F f) { foo(); bar(); f(); foobar(); } void MyFunction() { common([](){}); } void MyFunctionWithABonus() { common(&doBonusStuff); }
albo możesz po prostu tworzyć
prefix
isuffix
funkcjonować.void prefix() { foo(); bar(); } void suffix() { foobar(); } void MyFunction() { prefix(); suffix(); } void MyFunctionWithABonus() { prefix(); doBonusStuff(); suffix(); }
źródło
Coś takiego ładnie się nada:
template<bool bonus = false> void MyFunction() { foo(); bar(); if (bonus) { doBonusStuff(); } foobar(); }
Zadzwoń przez:
MyFunction<true>(); MyFunction<false>(); MyFunction(); // Call myFunction with the false template by default
Wszystkich „brzydkich” szablonów można uniknąć, dodając do funkcji kilka ładnych opakowań:
void MyFunctionAlone() { MyFunction<false>(); } void MyFunctionBonus() { MyFunction<true>(); }
Można znaleźć kilka ciekawych informacji na temat tej techniki tam . To jest „stary” artykuł, ale technika sama w sobie pozostaje całkowicie poprawna.
Pod warunkiem, że masz dostęp do ładnego kompilatora C ++ 17, możesz nawet rozwinąć tę technikę, używając constexpr if , w ten sposób:
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(); }
źródło
if constexpr (bonus) { doBonusStuff(); }
.doBonusStuff()
nie może nawet się skompilować z jakiegoś powodu w przypadku bez premii, zrobi to ogromną różnicę.Biorąc pod uwagę niektóre komentarze OP dotyczące debugowania, oto wersja, która wymaga
doBonusStuff()
kompilacji debugowania, ale nie wersji wydania (które definiująNDEBUG
):#if defined(NDEBUG) #define DEBUG(x) #else #define DEBUG(x) x #endif void MyFunctionWithABonus() { foo(); bar(); DEBUG(doBonusStuff()); foobar(); }
Możesz również użyć
assert
makra, jeśli chcesz sprawdzić warunek i zakończyć się niepowodzeniem, jeśli jest fałszywy (ale tylko w przypadku kompilacji debugowania; kompilacje wydania nie przeprowadzą sprawdzenia).Uważaj, jeśli
doBonusStuff()
ma efekty uboczne, ponieważ te efekty uboczne nie będą obecne w kompilacjach wydań i mogą unieważnić założenia przyjęte w kodzie.źródło
#if defined(NDEBUG)
bezpośrednio jest prawdopodobnie łatwiejsze.Oto niewielka odmiana odpowiedzi Jarod42 przy użyciu szablonów wariadycznych, aby dzwoniący mógł zapewnić zero lub jedną funkcję bonusową:
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(); }
Kod telefoniczny:
źródło
Inna wersja, wykorzystująca tylko szablony i bez funkcji przekierowujących, ponieważ powiedziałeś, że nie chcesz żadnego obciążenia związanego z uruchomieniem. O ile mi wiadomo, wydłuża to tylko czas kompilacji:
#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
Jest teraz tylko jedna wersja
MyFunc()
zbool
parametrem jako argumentem szablonu.źródło
bonus<false>()
wywołuje domyślną wersjębonus
szablonu (wiersze 9 i 10 przykładu), więc nie ma wywołania funkcji. Innymi słowy,MyFunc()
kompiluje się do jednego bloku kodu (bez żadnych warunków) iMyFunc<true>()
kompiluje do innego bloku kodu (bez żadnych warunków).Możesz użyć wysyłania tagów i prostego przeciążenia funkcji:
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(); }
Jest to łatwe do odczytania / zrozumienia, można je bez wysiłku rozszerzyć (i bez standardowych
if
klauzul - dodając więcej tagów) i oczywiście nie pozostawi śladu w czasie wykonywania.Składnia wywołania jest całkiem przyjazna, ale oczywiście można ją zawinąć w wywołania waniliowe:
void MyFunctionAlone() { MyFunction(Tag_DisableBonus{}); } void MyFunctionBonus() { MyFunction(Tag_EnableBonus{}); }
Wysyłanie tagów jest szeroko stosowaną ogólną techniką programowania. Oto fajny post o podstawach.
źródło