Mam klasę o nazwie, Writer
która ma taką funkcję writeVector
:
void Drawer::writeVector(vector<T> vec, bool index=true)
{
for (unsigned int i = 0; i < vec.size(); i++) {
if (index) {
cout << i << "\t";
}
cout << vec[i] << "\n";
}
}
Staram się nie mieć duplikatu kodu, jednocześnie martwiąc się o wydajność. W funkcji if (index)
sprawdzam każdą rundę mojej for
-loop, mimo że wynik jest zawsze taki sam. To jest przeciwko „martwieniu się o wydajność”.
Mogę łatwo tego uniknąć, umieszczając czek poza my for
-loop. Jednak otrzymam mnóstwo zduplikowanego kodu:
void Drawer::writeVector(...)
{
if (index) {
for (...) {
cout << i << "\t" << vec[i] << "\n";
}
}
else {
for (...) {
cout << vec[i] << "\n";
}
}
}
Są to więc dla mnie „złe” rozwiązania. To, o czym myślałem, to dwie funkcje prywatne, jedna z nich wychodzi poza indeks, a następnie wywołuje drugą. Drugi tylko przekracza wartość. Jednak nie mogę dowiedzieć się, jak go używać z moim programem, nadal potrzebowałbym if
sprawdzenia, aby zobaczyć, do którego zadzwonić ...
Zgodnie z problemem polimorfizm wydaje się być właściwym rozwiązaniem. Ale nie widzę, jak mam go tutaj używać. Jaki byłby preferowany sposób rozwiązania tego rodzaju problemu?
To nie jest prawdziwy program, interesuje mnie tylko dowiedzieć się, jak rozwiązać tego rodzaju problem.
źródło
Odpowiedzi:
Podaj treść pętli jako funktor. Jest wstawiany w czasie kompilacji, bez spadku wydajności.
Idea przekazywania tego, co się zmienia, jest wszechobecna w bibliotece standardowej C ++. Nazywa się to wzorcem strategii.
Jeśli możesz używać C ++ 11, możesz zrobić coś takiego:
Ten kod nie jest doskonały, ale masz pomysł.
W starym C ++ 98 wygląda to tak:
Ponownie, kod jest daleki od doskonałości, ale daje ci pomysł.
źródło
for(int i=0;i<100;i++){cout<<"Thank you!"<<endl;}
: D To jest rozwiązanie, którego szukałem, działa jak urok :) Można to poprawić kilkoma komentarzami (na początku miałem problemy ze zrozumieniem), ale dostałem, więc nie ma problemu :)cout << e << "\n";
), nadal występowałoby pewne powielenie kodu.Jeśli tak jest w rzeczywistości, predyktor gałęzi nie będzie miał problemu z przewidywaniem (stałego) wyniku. W związku z tym spowoduje to jedynie niewielkie obciążenie za błędne przewidywania w pierwszych kilku iteracjach. Nie ma się czym martwić, jeśli chodzi o wydajność
W tym przypadku zalecam trzymanie testu w pętli dla przejrzystości.
źródło
std::cout
do)Aby rozwinąć odpowiedź Alego, która jest całkowicie poprawna, ale wciąż powiela jakiś kod (część treści pętli, jest to niestety trudne do uniknięcia przy użyciu wzorca strategii) ...
W tym konkretnym przypadku powielanie kodu to niewiele, ale istnieje sposób, aby go jeszcze bardziej zmniejszyć, co jest przydatne, jeśli treść funkcji jest większa niż tylko kilka instrukcji .
Kluczem jest wykorzystanie zdolności kompilatora do ciągłej eliminacji zwijanego / martwego kodu . Możemy to zrobić, ręcznie mapując wartość czasu wykonywania na wartość czasu
index
kompilacji (łatwe do zrobienia, gdy jest tylko ograniczona liczba przypadków - w tym przypadku dwa) i używając argumentu szablonu innego niż typ, który jest znany podczas kompilacji -czas:W ten sposób otrzymujemy skompilowany kod, który jest odpowiednikiem twojego drugiego przykładu kodu (zewnętrzny
if
/ wewnętrznyfor
), ale bez jego duplikowania. Teraz możemy uczynić wersję szablonuwriteVector
tak skomplikowaną, jak chcemy, zawsze będzie jeden kawałek kodu do utrzymania.Zwróć uwagę, jak wersja szablonu (która przyjmuje stałą czasu kompilacji w postaci argumentu szablonu innego niż typ) i wersja bez szablonu (która przyjmuje zmienną czasu wykonywania jako argument funkcji) są przeciążone. Dzięki temu możesz wybrać najbardziej odpowiednią wersję w zależności od potrzeb, mając dość podobną, łatwą do zapamiętania składnię w obu przypadkach:
źródło
doWriteVector
bezpośrednio, ale zgadzam się, że nazwa była niefortunna. Po prostu zmieniłem go, aby miał dwie przeciążonewriteVector
funkcje (jeden szablon, drugi zwykłą funkcję), aby wynik był bardziej jednorodny. Dzieki za sugestie. ;)W większości przypadków kod jest już dobry pod względem wydajności i czytelności. Dobry kompilator jest w stanie wykryć niezmienniki pętli i dokonać odpowiednich optymalizacji. Rozważ następujący przykład, który jest bardzo zbliżony do twojego kodu:
Do kompilacji używam następującego wiersza poleceń:
Następnie zrzućmy zbiór:
Wynik montażu
write_vector
to:Widzimy, że na początku funkcji sprawdzamy wartość i przeskakujemy do jednej z dwóch możliwych pętli:
Oczywiście działa to tylko wtedy, gdy kompilator jest w stanie wykryć, że warunek jest faktycznie niezmienny. Zwykle doskonale sprawdza się w przypadku flag i prostych funkcji wbudowanych. Ale jeśli warunek jest „złożony”, rozważ zastosowanie innych odpowiedzi.
źródło