2013-04-16 14 views
6

Czy powinienem zwrócić uwagę na modyfikator const pracujący z typami pierwotnymi? Który z nich jest bardziej poprawny pod względem składni i dlaczego?C++ modyfikator const z typami pierwotnymi

Pierwsza wersja:

float Foo::bar(float a, float b) 
{ 
    return (a + b)/2.0f; 
} 

Druga wersja:

const float Foo::bar(const float a, const float b) 
{ 
    return (a + b)/2.0f; 
} 

Trzecia wersja:

float Foo::bar(const float a, const float b) 
{ 
    return (a + b)/2.0f; 
} 

wiem zmienne prymitywne wpisane są kopiowane przy przejściu do jakiejś metody, ale jaki sposób jest bardziej przejrzysty?

Odpowiedz

13

Powiedziałbym, że trzecia wersja jest najbardziej "poprawna".

Poinformujesz kompilator, że argumenty są const, co jest poprawne, ponieważ ich nie modyfikujesz. Może to pomóc kompilatorowi w optymalizowaniu przekazywania argumentów, a także w obliczeniach.

Typ zwrotu nie jest const, ponieważ dzwoniący może chcieć zmodyfikować zwróconą wartość. Jeśli osoba dzwoniąca nie chce zmodyfikować zwróconej wartości, to do dzwoniącego należy przypisanie jej do zmiennej const.

Chciałbym również dodałem const do deklaracji funkcji, ponieważ funkcja nie zmienia nic w obiekcie:

float Foo::bar(const float a, const float b) const 
+1

Funkcja członka +1 stałego (choć pomocne uzupełnienie tego, co zadawał OP). – wmorrison365

+2

"dzwoniący może chcieć zmodyfikować zwróconą wartość" - chociaż będą potrzebowali 'const_cast' (lub rzutowania w stylu C), ponieważ jest to wyrażenie rvalue typu pierwotnego. Więc nawet jeśli nie jest to const, nie mogą uzyskać odniesienia do niego bez rzutowania. Nie jestem pewien, czy jest to coś, * czego * naprawdę * potrzebujemy wspierać, ale co ważniejsze, jeśli twoja funkcja zwraca obiekt typu const o 'const', który jest ogólnie zły, ponieważ uniemożliwia to przejście z niego lub wywołanie jego funkcji składowych. –

+0

Jeszcze jeden szczegół, jeśli typem powrotu był 'bool', dobrze jest go" const "unikać przypadkowego przypisania w wyrażeniach warunkowych. Można tego uniknąć, wykręcając ostrzeżenia kompilatora, ale po ugryzieniu dwa razy nieśmiałe. –

0

Najlepszym sposobem jest to AFAIK:

const float Foo::bar(const float& a, const float& b) const 
{ 
    return (a + b)/2.0f; 
} 

ta informuje co mpiler że notthing zmieni, a także nie ma potrzeby, aby skopiować wartości parametrów.

+4

Dlaczego jest lepszy od innych? Jeśli przekazujesz wartość, nie modyfikujesz oryginalnych danych.Jeśli przekazujesz referencję - nie ma kopii wartości, ale istnieje kopia samego odwołania – nogard

+0

Może to również spowodować dodatkowy narzut związany z niedokładnością (jeśli nie jest zoptymalizowany), ponieważ odwołanie jest w zasadzie wskaźnikiem za kulisami. –

1

Nie ma żadnej różnicy w ogóle pomiędzy 2 i 3rd wersjach. Wybierz ten, który jest najkrótszy do wpisania :)

Jest niewielka różnica między 1 a 3. Możesz preferować trzecią, jeśli obawiasz się przypadkowego zmodyfikowania wewnątrz funkcji funkcji a i b.

+0

Istnieje jednak różnica między 2. i 3. odpowiedzią. Drugi zwraca "const float", co oznacza, że ​​zwracana wartość nie może zostać zmieniona. w trzeciej można oczywiście. – JasoonS

0

Krótka odpowiedź: To nie ma znaczenia.

Długa odpowiedź: Ponieważ przekazujesz dwa argumenty według wartości i zwracasz argument według wartości. Każda z nich jest w porządku, ale częściej będziesz widzieć pierwszą wersję.

Jeśli przekazujesz przez odniesienie (jak sugerowali inni), to ma to znaczenie i powinieneś użyć referencji const. Jednak przekazywanie typów pierwotnych przez odniesienie nie daje żadnych korzyści ani nie ma sensu (jeśli jest odniesieniem do stałych). Powodem tego jest to, że przejście przez prymitywne typy według wartości nie spowoduje żadnego narzutu w porównaniu do przechodzenia prymitywu przez odniesienie.

1

Powiem, że prymitywami może być skuteczniejsze ich kopiowanie. Po przekazaniu referencji kompilator musi jeszcze przekazać bajty na stosie, a następnie musi usunąć adres, aby uzyskać zawartość.

Ponadto przekazywanie według wartości eliminuje wszelkie możliwe problemy dotyczące współbieżności/zmienności dotyczące pamięci przekazywanych danych.

Jest to przypadek "nie próbuj optymalizować tutaj".

Powracanie przez const jest stylem. Zwykle nie, inni wolą na wypadek, gdyby ktoś chciał coś zrobić ze zwróconą wartością. Następnie znajdziesz ludzi, którzy zwracają je przez odniesienie wartości r ...

Zazwyczaj wybieram pierwszą opcję. Drugą alternatywą jest przekazywanie wartości (nie jest konieczne używanie const) i zwracanie przez wartość const.

+0

"Następnie znajdziesz ludzi, którzy zwracają je przez referencję wartości r" - co prowokuje UB, ponieważ nie możesz zwrócić referencji rwartości do lokalnego obiektu, tak jak nie możesz zwrócić zwykłego odniesienia do lokalnego obiektu. Mam nadzieję, że tych ludzi można szybko wyleczyć. –

2

Po pierwsze, wszystkie podane przez ciebie definicje są poprawne składniowo. Jeśli się kompilują, to są poprawne pod względem składni.

Kwalifikator dla parametrów ma tylko jeden cel: uniemożliwić modyfikowanie argumentów funkcji przez modyfikację argumentów funkcji const.

W konkretnym przypadku przykładowego kodu metoda Foo::bar nie modyfikuje argumentów, więc użycie kwalifikatora const nie ma żadnego wpływu.

Jednak domyślnie możesz używać domyślnie const i usuwać go tylko w sytuacjach, w których chcesz zezwolić na modyfikacje. Stąd zastosowanie go do parametrów Foo::bar jest dobrym pomysłem. Myślę, że to dobra praktyka, choć przyznam, że rzadko jej używam, ze względu na nieco hałasu, który może powodować zmniejszenie czytelności.

Inną kwestią, którą należy wziąć pod uwagę, jest to, że w przypadku typów pierwotnych, a dokładniej typów, które nie są wskaźnikami lub nie zawierają wskaźników, modyfikowanie argumentu przekazanego przez wartość (tj. Nie przez odniesienie) nie będzie miało żadnych skutków ubocznych: parametry te typy naprawdę działają jako zainicjowane zmienne lokalne (które mogą być przydatne, ale mogą również być mylące). W przypadku wskaźników każda modyfikacja wskazanych danych wycieknie do świata zewnętrznego. To kolejny dobry powód, aby użyć kwalifikatora const zarówno na wskaźniku, jak i na wskazanej części typu.

Podsumowując, użycie kwalifikatora const w jak największym stopniu pomoże uczynić kod mniej podatnym na błędy, a także pomoże kompilatorowi zoptymalizować wynikowy program.

Korzystanie z odniesienia dla tych typów nie należy jednak dokonywać żadnych znaczących zmian, jeśli te typy opisują wartości montażu w rejestrze procesora (co zwykle ma miejsce),

Tak, wszystkie trzy wersje metody powinny sprowadza się do tego samego wygenerowanego kodu zespołu.

W szczególnym przypadku pierwotnych typów zwracanych nie ma znaczenia. wartość zwracana może być konwertowana do iz powrotem do kwalifikowanej wartości.

Inni wspomnieli także o interesie kwalifikatora const na samej funkcji. Chociaż z pierwotnego zakresu pytań, powiem również, że jest rzeczywiście lepiej, gdy to możliwe (jak na Foo::bar), aby zakwalifikować funkcję jako const.

+1

"pomoże również optymalizować kompilator" - * będzie * jest trochę mocny. Zadaję pytanie http://stackoverflow.com/questions/1121791, dawno temu, a odpowiedź okazała się być taka, że ​​const-qualifying prymitywna zmienna pomogła gcc 3 zoptymalizować, ale gcc 4 był wystarczająco inteligentny, aby nie potrzebować Wsparcie. –

+0

cóż, może. W przypadku typów pierwotnych może to nie ma większego znaczenia. – didierc

Powiązane problemy