Użycie „const” dla parametrów funkcji


396

Jak daleko posuniesz się const? Czy po prostu wykonujesz funkcje, constgdy jest to konieczne, czy chodzisz po całym świecie i używasz go wszędzie? Na przykład wyobraź sobie prostego mutatora, który przyjmuje pojedynczy parametr boolowski:

void SetValue(const bool b) { my_val_ = b; }

Czy to jest constnaprawdę przydatne? Osobiście wybieram go szeroko, łącznie z parametrami, ale w tym przypadku zastanawiam się, czy warto?

Byłem również zaskoczony, gdy dowiedziałem się, że można pominąć constparametry w deklaracji funkcji, ale można to uwzględnić w definicji funkcji, np .:

plik .h

void func(int n, long l);

plik .cpp

void func(const int n, const long l)

Czy jest tego powód? Wydaje mi się to trochę niezwykłe.


Nie zgadzam się. Plik .h musi mieć również definicje const. Jeśli nie, to jeśli parametry const zostaną przekazane do funkcji, kompilator wygeneruje błąd, ponieważ prototyp w pliku .h nie ma definicji const.
selwyn

10
Zgadzam się. :-) (Z pytaniem, a nie ostatnim komentarzem!) Jeśli wartość nie powinna zostać zmieniona w treści funkcji, może to pomóc zatrzymać głupie błędy == lub = błędy, nigdy nie należy umieszczać const w obu, (jeśli jest przekazywana przez wartość, musisz inaczej) Nie jest to jednak wystarczająco poważne, aby wdawać się w argumenty na ten temat!
Chris Huang-Leaver,

19
@selwyn: Nawet jeśli przekażesz const int do funkcji, zostanie ona skopiowana (ponieważ nie jest to odwołanie), więc const-ness nie ma znaczenia.
czerwiec

1
Ta sama debata odbywa się w tym pytaniu: stackoverflow.com/questions/1554750/…
Częściowe

5
Zdaję sobie sprawę, że ten post ma kilka lat, ale jako nowy programista zastanawiałem się nad tym pytaniem i natknąłem się na tę rozmowę. Moim zdaniem, jeśli funkcja nie powinna zmieniać wartości, zarówno odniesienia, jak i kopii wartości / obiektu, powinna być stała. Jest bezpieczniejszy, samo dokumentuje się i jest bardziej przyjazny dla debugowania. Nawet dla najprostszej funkcji, która ma jedną instrukcję, nadal używam const.

Odpowiedzi:


187

Powodem jest to, że const dla parametru ma zastosowanie tylko lokalnie w obrębie funkcji, ponieważ działa ona na kopię danych. Oznacza to, że podpis funkcji jest w rzeczywistości taki sam. Jednak prawdopodobnie jest to zły styl.

Ja osobiście nie używam const z wyjątkiem parametrów odniesienia i wskaźnika. W przypadku skopiowanych obiektów nie ma to większego znaczenia, chociaż może być bezpieczniejszy, ponieważ sygnalizuje zamiary w obrębie funkcji. To naprawdę wyrok sądu. Zwykle jednak używam const_iterator podczas zapętlania czegoś i nie mam zamiaru go modyfikować, więc myślę, że każdy z nich ma własny, o ile rygorystycznie zachowana jest poprawność const dla typów referencyjnych.


57
Nie mogę się zgodzić z częścią „złego stylu”. Usunięcie constz prototypów funkcji ma tę zaletę, że nie trzeba zmieniać pliku nagłówka, jeśli zdecydujesz się constpóźniej zrezygnować z części implementacyjnej.
Michał Górny,

4
„Osobiście nie używam const z wyjątkiem parametrów odniesienia i wskaźnika”. Być może powinieneś wyjaśnić, że dla „Zwykle nie używam zbędnych kwalifikatorów w deklaracjach funkcji, ale używam consttam, gdzie robi to użyteczną różnicę”.
Deduplicator

3
Nie zgadzam się z tą odpowiedzią. Odchylam się w drugą stronę i constzawsze zaznaczam parametry ; jest bardziej ekspresyjny. Kiedy czytam kod innej osoby, używam takich małych wskaźników, aby ocenić, jak wiele uwagi poświęcają na pisanie kodu wraz z takimi rzeczami, jak magiczne liczby, komentowanie i właściwe używanie wskaźnika, itp.
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }Spróbuj tego. Oczywiście ++anie ma tam nic do roboty, ale można ją znaleźć w długiej funkcji napisanej przez więcej niż jednego programistę przez długi czas. Zdecydowanie sugerowałbym napisanie int getDouble( const int a ){ //... }, które wygeneruje błąd kompilacji podczas wyszukiwania ++a;.
dom_beau

3
Wszystko zależy od tego, kto potrzebuje jakich informacji. Podajesz parametr według wartości, aby osoba dzwoniąca nie musiała nic wiedzieć na temat tego, co robisz (wewnętrznie). Więc napisz class Foo { int multiply(int a, int b) const; }w nagłówku. W swojej implementacji zależy ci na tym, abyś obiecał, że nie zmienisz, ai bdlatego int Foo::multiply(const int a, const int b) const { }ma to sens. (Sidenote: zarówno program wywołujący, jak i implementacja dbają o to, aby funkcja nie zmieniała swojego Fooobiektu, a więc const na końcu deklaracji)
CharonX

415

„const nie ma sensu, gdy argument jest przekazywany przez wartość, ponieważ nie będziesz modyfikował obiektu obiektu wywołującego”.

Źle.

Chodzi o samodzielne udokumentowanie kodu i założeń.

Jeśli w twoim kodzie pracuje wiele osób, a twoje funkcje nie są trywialne, powinieneś zaznaczyć „const” dowolne i wszystko, co możesz. Pisząc kod przemysłowy, zawsze powinieneś zakładać, że twoi współpracownicy są psychopatami, próbującymi cię zdobyć w każdy możliwy sposób (zwłaszcza, że ​​często w przyszłości jesteś sobą).

Poza tym, jak ktoś wcześniej wspomniał, może to pomóc kompilatorowi nieco zoptymalizować rzeczy (choć jest to długa szansa).


41
Kompletnie się zgadzam. Chodzi o komunikowanie się z ludźmi i ograniczanie tego, co można zrobić za pomocą zmiennej, do tego, co należy zrobić.
Len Holgate,

19
Głosowałem za tym. Myślę, że rozrzedzasz to, co próbujesz wskazać za pomocą const, gdy zastosujesz to do prostych argumentów przekazywania wartości.
tonylo,

26
Głosowałem za tym. Deklaracja parametru „const” dodaje do parametru informacje semantyczne. Podkreślają, co zamierzał pierwotny autor kodu, co z czasem ułatwi utrzymanie kodu.
Richard Corden,

13
@tonylo: źle zrozumiałeś. Chodzi o oznaczenie zmiennej lokalnej jako const w bloku kodu (która jest funkcją). Zrobiłbym to samo dla każdej zmiennej lokalnej. Ortogonalne jest posiadanie niezmiennie poprawnego interfejsu API, co w rzeczywistości jest również ważne.
rlerallut

56
I może łapać błędy wewnątrz funkcji - jeśli wiesz, że parametru nie należy zmieniać, to zadeklarowanie const oznacza, że ​​kompilator poinformuje cię, jeśli przypadkowo go zmodyfikujesz.
Adrian

156

Czasami (zbyt często!) Muszę rozwikłać kod C ++ innej osoby. I wszyscy wiemy, że kod C ++ innej osoby jest z definicji kompletnym bałaganem :) Więc pierwszą rzeczą, którą robię, aby odszyfrować lokalny przepływ danych, jest const w każdej definicji zmiennej, dopóki kompilator nie zacznie szczekać. Oznacza to również argumenty wartości kwalifikującej const, ponieważ są to tylko fantazyjne zmienne lokalne zainicjowane przez program wywołujący.

Ach, chciałbym, żeby zmienne były domyślnie const i zmienne były wymagane dla zmiennych non-const :)


4
„Chciałbym, żeby zmienne były domyślnie stałe” - oksymoron? 8-) Poważnie, jak „ułożenie” wszystkiego pomaga rozplątać kod? Jeśli oryginalny pisarz zmienił rzekomo stały argument, skąd wiesz, że var miał być stały? Co więcej, zdecydowana większość zmiennych (nie-argumentowych) ma być ... zmiennymi. Więc kompilator powinien się przerwać wkrótce po rozpoczęciu procesu, nie?
ysap

8
@ysap, 1. Oznaczenie const w miarę możliwości pozwala mi zobaczyć, które części się poruszają, a które nie. Z mojego doświadczenia wynika, że ​​wielu mieszkańców jest de facto stałymi, a nie na odwrót. 2. „Stała zmienna” / „Zmienna niezmienna” może brzmieć jak oksymoron, ale jest standardową praktyką w językach funkcjonalnych, a także w niektórych językach niefunkcjonalnych; patrz Rust na przykład: doc.rust-lang.org/book/variable-bindings.html
Constantin

1
Również teraz standardem w niektórych okolicznościach w c ++; na przykład lambda [x](){return ++x;}jest błędem; patrz tutaj
anatolyg

10
Zmienne są constdomyślnie „ ” w Rust :)
feniks

Zmienne niekoniecznie muszą być przypisywalne, aby mogły się różnić; wartość, którą inicjują, może się również różnić w czasie wykonywania.
Sphynx

80

Poniższe dwa wiersze są funkcjonalnie równoważne:

int foo (int a);
int foo (const int a);

Oczywiście nie będziesz mógł modyfikować aw cielefoo jeśli jest zdefiniowane w drugi sposób, ale nie ma różnicy z zewnątrz.

Gdzie constnaprawdę jest przydatna jest w parametrach referencyjnych lub palików:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

To oznacza, że ​​foo może przyjąć duży parametr, być może strukturę danych o wielkości gigabajtów, bez kopiowania. Mówi także dzwoniącemu: „Foo * nie zmieni zawartości tego parametru”. Przekazanie stałego odwołania pozwala również kompilatorowi na podejmowanie określonych decyzji dotyczących wydajności.

*: Chyba że odrzuca ciągłość, ale to kolejny post.


3
Nie o to chodzi w tym pytaniu; oczywiście dla argumentów odwołujących się lub wskazywanych na wskazane dobrze jest użyć const (jeśli wartość odniesienia lub wskazywana na nie jest modyfikowana). Zauważ, że to nie parametr jest const w twoim przykładzie wskaźnika; parametr wskazuje na to.
tml

> Przekazanie stałego odwołania pozwala również kompilatorowi na podejmowanie określonych decyzji dotyczących wydajności. klasyczny błąd - kompilator musi ustalić sobie const-ności, słowo kluczowe const nie pomoże z tym dzięki wskaźnik aliasing i const_cast
jheriko

73

Dodatkowe zbędne stałe są złe z punktu widzenia API:

Umieszczenie w kodzie dodatkowych zbędnych stałych dla parametrów typu wewnętrznego przekazywanych przez wartość zaśmieca interfejs API , nie dając żadnej znaczącej obietnicy użytkownikowi wywołującemu lub użytkownikowi interfejsu API (utrudnia to tylko implementację).

Zbyt wiele „stałych” w interfejsie API, gdy nie są potrzebne, jest jak „ płaczący wilk” ”, w końcu ludzie zaczną ignorować „stały”, ponieważ jest wszędzie i nic nie znaczy przez większość czasu.

Argument „reductio ad absurdum” do dodatkowych stałych w API jest dobry dla tych dwóch pierwszych punktów byłoby, gdyby więcej parametrów stałych było dobrych, to każdy argument, który może mieć na nim stałą, POWINIEN mieć na sobie stałą. W rzeczywistości, gdyby naprawdę był tak dobry, chciałbyś, aby const był domyślnym parametrem i miał słowo kluczowe takie jak „mutable” tylko wtedy, gdy chcesz zmienić parametr.

Spróbujmy więc wprowadzić const gdziekolwiek możemy:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

Rozważ wiersz kodu powyżej. Deklaracja jest nie tylko bardziej zaśmiecona, dłuższa i trudniejsza do odczytania, ale trzy z czterech „stałych” słów kluczowych może bezpiecznie zignorować użytkownik interfejsu API. Jednak dodatkowe użycie „const” sprawiło, że druga linia potencjalnie NIEBEZPIECZNA!

Dlaczego?

Szybkie błędne odczytanie pierwszego parametru char * const buffermoże sprawić, że pomyślisz, że nie zmodyfikuje on pamięci w przekazywanym buforze danych - nie jest to jednak prawda! Zbędne „stałe” mogą prowadzić do niebezpiecznych i niepoprawnych założeń dotyczących interfejsu API podczas szybkiego skanowania lub błędnego odczytu.


Zbędne const są również złe z punktu widzenia implementacji kodu:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

Jeśli FLEXIBLE_IMPLEMENTATION nie jest prawdą, to interfejs API „obiecuje”, że nie zaimplementuje funkcji w pierwszy sposób poniżej.

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

To bardzo głupia obietnica. Dlaczego należy składać obietnicę, która w ogóle nie przyniesie korzyści dzwoniącemu, a jedynie ograniczy wdrożenie?

Oba są doskonale poprawnymi implementacjami tej samej funkcji, więc niepotrzebnie przywiązujesz jedną rękę do pleców.

Co więcej, jest to bardzo płytka obietnica, którą łatwo (i legalnie obchodzi).

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

Słuchaj, zaimplementowałem go w ten sposób, mimo że obiecałem, że tego nie zrobię - tylko używając funkcji otoki. To tak, jakby złoczyńca obiecał, że nie zabije kogoś w filmie, i zamiast tego poleci swojemu poplecznikowi.

Te zbędne const są warte nie więcej niż obietnica złego filmowca.


Ale zdolność do kłamstwa staje się jeszcze gorsza:

Zostałem oświecony, że można niedopasować const w nagłówku (deklaracji) i kodzie (definicji) za pomocą fałszywego const. Zwolennicy const-happy twierdzą, że jest to dobra rzecz, ponieważ pozwala ona umieszczać const tylko w definicji.

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

Jednak odwrotność jest prawdziwa ... możesz umieścić fałszywą stałą tylko w deklaracji i zignorować ją w definicji. To sprawia, że ​​zbędna stała w interfejsie API staje się bardziej okropną rzeczą i okropnym kłamstwem - zobacz ten przykład:

class foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

W rzeczywistości wszystko, co jest zbędne, powoduje, że kod implementatora jest mniej czytelny, zmuszając go do użycia innej lokalnej kopii lub funkcji opakowania, gdy chce zmienić zmienną lub przekazać zmienną przez odwołanie inne niż const.

Spójrz na ten przykład. Który jest bardziej czytelny? Czy jest oczywiste, że jedynym powodem dodatkowej zmiennej w drugiej funkcji jest to, że jakiś projektant API wrzucił zbędną stałą?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

Mam nadzieję, że czegoś się tutaj nauczyliśmy. Zbędny const to zaśmiecony API, denerwujący nag, płytka i bezsensowna obietnica, niepotrzebna przeszkoda, a czasami prowadzi do bardzo niebezpiecznych błędów.


9
Dlaczego opinie negatywne? Jest to o wiele bardziej pomocne, jeśli zostawisz krótki komentarz do recenzji.
Adisak

7
Cały sens używania argumentu const polega na tym, że oznaczona linia zawodzi (plist = pnext). Jest rozsądnym środkiem bezpieczeństwa, aby argument funkcji był niezmienny. Zgadzam się z twoją tezą, że są złe w deklaracjach funkcji (ponieważ są zbyteczne), ale mogą służyć swoim celom w bloku implementacyjnym.
touko

23
@Adisak Nie widzę nic złego w twojej odpowiedzi, per se, ale z twoich komentarzy wynika, że ​​brakuje ci ważnego punktu. Definicja / implementacja funkcji nie jest częścią API, która jest jedynie deklaracją funkcji . Jak już powiedziałeś, deklarowanie funkcji za pomocą parametrów const jest bezcelowe i dodaje bałaganu. Jednak użytkownicy interfejsu API mogą nigdy nie musieć widzieć jego implementacji. Tymczasem implementator może zdecydować o zakwalifikowaniu niektórych parametrów w definicji funkcji WYŁĄCZNIE dla przejrzystości, co jest całkowicie w porządku.
jw013,

17
@ jw013 jest poprawny void foo(int)i void foo(const int)mają dokładnie tę samą funkcję, a nie przeciążenia. ideone.com/npN4W4 ideone.com/tZav9R Stała tutaj jest tylko szczegółem implementacji treści funkcji i nie ma wpływu na rozdzielczość przeciążenia. Pozostaw const z deklaracji, aby uzyskać bezpieczniejszy i bardziej czysty interfejs API, ale wstaw const do definicji , jeśli nie zamierzasz modyfikować skopiowanej wartości.
Oktalist

3
@Adisak Wiem, że to jest stare, ale wierzę, że poprawne użycie publicznego API byłoby na odwrót. W ten sposób programiści pracujący przy elementach wewnętrznych nie popełniają błędów, na przykład pi++wtedy, gdy nie powinni.
CoffeeandCode

39

stała powinna być domyślną w C ++. Lubię to :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

8
Dokładnie moje myśli.
Constantin,

24
Zgodność z C jest zbyt ważna, przynajmniej dla osób projektujących C ++, aby nawet to rozważyć.

4
Ciekawe, nigdy o tym nie myślałem.
Dan.

6
Podobnie unsignedpowinno być domyślnym w C ++. Jak to: int i = 5; // i is unsignedi signed int i = 5; // i is signed.
hkBattousai

25

Kiedy kodowałem C ++ do życia, stworzyłem wszystko, co mogłem. Korzystanie z const jest świetnym sposobem na pomoc kompilatorowi. Na przykład ograniczenie wartości zwracanych przez metodę może uchronić Cię przed literówkami, takimi jak:

foo() = 42

kiedy miałeś na myśli:

foo() == 42

Jeśli zdefiniowano foo (), aby zwracało odwołanie inne niż const:

int& foo() { /* ... */ }

Kompilator z przyjemnością pozwoli ci przypisać wartość do anonimowego tymczasowego zwróconego przez wywołanie funkcji. Robiąc to const:

const int& foo() { /* ... */ }

Eliminuje tę możliwość.


6
Z jakim kompilatorem to działało? GCC podaje błąd podczas próby kompilacji foo() = 42: błąd: wymagana wartość jako lewy argument przypisania
gavrie

To jest po prostu nieprawidłowe. foo () = 42 to to samo co 2 = 3, tj. błąd kompilatora. A zwracanie stałej jest całkowicie bezcelowe. Nie robi nic dla wbudowanego typu.
Josh

2
Spotkałem się z tym użyciem const i mogę powiedzieć, że w końcu powoduje o wiele więcej problemów niż korzyści. Wskazówka: const int foo()jest innego rodzaju niż int foo(), co powoduje duże problemy, jeśli używasz takich wskaźników, jak wskaźniki funkcji, systemy sygnałów / gniazd lub boost :: bind.
Mephane,

2
Poprawiłem kod, aby zawierał zwracaną wartość referencyjną.
Avdi

Czy nie jest const int& foo()efektywnie taki sam jak int foo()ze względu na optymalizację wartości zwrotu?
Zantier

15

Dobra dyskusja na ten temat znajduje się w moderowanych tutaj starych artykułach „Guru tygodnia” na comp.lang.c ++ .

Odpowiedni artykuł GOTW jest dostępny na stronie internetowej Herb Sutter tutaj .


1
Herb Sutter jest naprawdę mądrym facetem :-) Zdecydowanie warto przeczytać i zgadzam się ze WSZYSTKIMI jego punktami.
Adisak

2
Dobry artykuł, ale nie zgadzam się z nim co do argumentów. Ustawiam je również jako stałe, ponieważ są one jak zmienne i nigdy nie chcę, aby ktokolwiek dokonywał jakichkolwiek zmian w moich argumentach.
QBziZ

9

Mówię const swoje parametry wartości.

Rozważ tę funkcję buggy:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

Jeśli parametr number byłby stały, kompilator zatrzymałby się i ostrzegłby nas o błędzie.


2
innym sposobem jest użycie if (0 == liczba) ... else ...;
Johannes Schaub - litb

5
@ ChrisHuang-Leaver Okropne, że nie, jeśli mówisz jak Yoda, to robisz: stackoverflow.com/a/2430307/210916
MPelletier

GCC / Clang -Wall daje ci -Wparentheses, co wymaga, abyś zrobił to „if ((liczba = 0))”, jeśli to jest rzeczywiście to, co zamierzałeś zrobić. Który działa dobrze jako substytut bycia Yoda.
Jetski typu S

8

Używam const na parametrach funkcji, które są referencjami (lub wskaźnikami), które są tylko danymi wejściowymi i nie będą modyfikowane przez funkcję. Oznacza to, że celem użycia odwołania jest uniknięcie kopiowania danych i niedopuszczenie do zmiany przekazywanego parametru.

Nałożenie const na parametr boolean b w twoim przykładzie ogranicza tylko implementację i nie przyczynia się do interfejsu klasy (chociaż zwykle nie zaleca się zmiany parametrów).

Podpis funkcji dla

void foo(int a);

i

void foo(const int a);

jest taki sam, co wyjaśnia twoje .c i .h

Asaf


6

Jeśli używasz operatorów ->*lub .*, jest to konieczne.

To zapobiega pisaniu czegoś takiego

void foo(Bar *p) { if (++p->*member > 0) { ... } }

co prawie zrobiłem teraz i które prawdopodobnie nie robi tego, co zamierzacie.

Chciałem powiedzieć

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

i gdybym umieścić constpomiędzy Bar *i pkompilator powiedziałby mi o tym.


4
Natychmiast sprawdziłbym referencję dotyczącą pierwszeństwa operatorów, kiedy mam zamiar wymieszać, że wielu operatorów (jeśli jeszcze nie znam 100%), więc IMO to nie problem.
mk12

5

Ach, trudny. Z jednej strony deklaracja jest umową i naprawdę nie ma sensu przekazywać argumentu const według wartości. Z drugiej strony, jeśli spojrzysz na implementację funkcji, dajesz kompilatorowi większe możliwości optymalizacji, jeśli zadeklarujesz stałą argumentu.


5

const nie ma sensu, gdy argument jest przekazywany przez wartość, ponieważ nie będziesz modyfikował obiektu obiektu wywołującego.

const powinno być preferowane przy przekazywaniu przez referencję, chyba że celem funkcji jest modyfikacja przekazywanej wartości.

Wreszcie funkcja, która nie modyfikuje bieżącego obiektu (this), może i prawdopodobnie powinna zostać zadeklarowana jako const. Przykład jest poniżej:

int SomeClass::GetValue() const {return m_internalValue;}

Jest to obietnica niezmodyfikowania obiektu, do którego zastosowano to wywołanie. Innymi słowy, możesz zadzwonić:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

Jeśli funkcja nie była stała, spowodowałoby to ostrzeżenie kompilatora.


5

Oznaczanie parametrów wartości „const” jest zdecydowanie rzeczą subiektywną.

Jednak tak naprawdę wolę oznaczać const parametry wartości, tak jak w twoim przykładzie.

void func(const int n, const long l) { /* ... */ }

Dla mnie wartość wyraźnie wskazuje, że wartości parametrów funkcji nigdy nie są zmieniane przez funkcję. Na początku będą miały tę samą wartość, co na końcu. Dla mnie jest to część utrzymywania bardzo funkcjonalnego stylu programowania.

W przypadku krótkiej funkcji jest zapewne stratą czasu / przestrzeni, aby mieć tam „const”, ponieważ zwykle dość oczywiste jest, że argumenty nie są modyfikowane przez funkcję.

Jednak w przypadku większej funkcji jest to forma dokumentacji implementacyjnej i jest egzekwowana przez kompilator.

Mogę być pewien, że jeśli wykonam jakieś obliczenia za pomocą „n” i „l”, mogę refaktoryzować / przenieść to obliczenie bez obawy o uzyskanie innego wyniku, ponieważ przegapiłem miejsce, w którym jedno lub oba zostały zmienione.

Ponieważ jest to szczegół implementacji, nie musisz deklarować const parametru wartości const w nagłówku, tak samo jak nie musisz deklarować parametrów funkcji o takich samych nazwach, jakich używa implementacja.


4

Może to nie będzie prawidłowy argument. ale jeśli zwiększymy wartość stałej zmiennej w kompilatorze funkcji, otrzymamy błąd: „ błąd: przyrost parametru tylko do odczytu ”. oznacza to, że możemy użyć słowa kluczowego const jako sposobu, aby zapobiec przypadkowej modyfikacji naszych zmiennych w funkcjach (których nie powinniśmy / tylko do odczytu). więc jeśli przypadkowo to zrobiliśmy podczas kompilacji, kompilator poinformuje nas o tym. jest to szczególnie ważne, jeśli nie jesteś jedynym, który pracuje nad tym projektem.


3

Kiedykolwiek to możliwe, używam const. (Lub inne odpowiednie słowo kluczowe dla języka docelowego.) Robię to wyłącznie dlatego, że pozwala to kompilatorowi na dokonanie dodatkowych optymalizacji, których inaczej nie byłby w stanie dokonać. Ponieważ nie mam pojęcia, czym mogą być te optymalizacje, zawsze robię to, nawet jeśli wydaje się to głupie.

Z tego, co wiem, kompilator może bardzo dobrze zobaczyć parametr stałej wartości i powiedzieć: „Hej, ta funkcja i tak go nie modyfikuje, więc mogę przejść przez odniesienie i zapisać kilka cykli zegara”. Nie sądzę, że kiedykolwiek zrobiłoby coś takiego, ponieważ zmienia podpis funkcji, ale ma sens. Może robi to różne manipulacje na stosie lub coś w tym rodzaju ... Chodzi o to, że nie wiem, ale wiem, że próba bycia mądrzejszym niż kompilator prowadzi tylko do wstydu.

C ++ ma dodatkowy bagaż z ideą stałej poprawności, więc staje się jeszcze ważniejszy.


Chociaż może to w niektórych przypadkach pomóc, podejrzewam, że możliwość promowania optymalizacji jest dramatycznie zawyżona jako korzyść const. Raczej chodzi o ustalenie zamiaru w trakcie implementacji i późniejsze złapanie „cienkich” (przypadkowe zwiększenie niewłaściwej zmiennej lokalnej, ponieważ tak nie było const). Równolegle dodam również, że kompilatory są bardzo mile widziane do zmiany sygnatur funkcji, w tym sensie, że funkcje można wstawiać, a po wstawieniu można zmienić cały sposób ich działania; dodawanie lub usuwanie odniesień, tworzenie literałów „zmiennych” itp. mieszczą się w zasadzie „jak gdyby”
underscore_d

3

1. Najlepsza odpowiedź na podstawie mojej oceny:

Odpowiedź @Adisak jest najlepszą odpowiedzią tutaj na podstawie mojej oceny. Zauważ, że ta odpowiedź jest częściowo najlepszy, ponieważ jest również najbardziej wycofał się z przykładami prawdziwy kod , oprócz korzystania dźwięk i dobrze przemyślanej logiki.

2. Moje własne słowa (zgadzając się z najlepszą odpowiedzią):

  1. W przypadku wartości dodanej nie ma korzyści z dodawania const. Wszystko, co robi to:
    1. ogranicz implementatora, aby musiał wykonywać kopię za każdym razem, gdy chce zmienić parametr wejściowy w kodzie źródłowym (która zmiana i tak nie miałaby żadnych skutków ubocznych, ponieważ to, co zostało przekazane, jest już kopią, ponieważ jest przekazywane przez wartość). I często zmiana parametru wejściowego przekazywanego przez wartość służy do implementacji funkcji, więc dodawanie constwszędzie może to utrudnić.
    2. i dodawanie constniepotrzebnie zaśmieca kod consts wszędzie, odciągając uwagę od tych, constktóre są naprawdę niezbędne do bezpiecznego kodu.
  2. Jednak podczas korzystania ze wskaźników lub odniesieńconst jest niezwykle ważne w razie potrzeby i musi być stosowane, ponieważ zapobiega niepożądanym efektom ubocznym z utrzymującymi się zmianami poza funkcją, dlatego też każdy wskaźnik lub odniesienie musi być używane, constgdy parm jest tylko danymi wejściowymi, nie wyjście. Używanie const tylko parametrów przekazywanych przez referencję lub wskaźnik ma dodatkową zaletę, ponieważ sprawia, że naprawdę oczywiste jest, które parametry są wskaźnikami lub referencjami. Jest jeszcze jedna rzecz, aby wystarczyć i powiedzieć „Uważaj! Dowolny paramconst obok jest referencją lub wskaźnikiem!”.
  3. To, co opisałem powyżej, to często konsensus osiągany w profesjonalnych organizacjach zajmujących się oprogramowaniem, w którym pracowałem i został uznany za najlepszą praktykę. Czasami nawet zasada była ścisła: „nigdy nie używaj const dla parametrów przekazywanych przez wartość, ale zawsze używaj jej dla parametrów przekazywanych przez referencję lub wskaźnik, jeśli są one tylko danymi wejściowymi”.

3. Słowa Google (zgadzając się ze mną i najlepsza odpowiedź):

(Z „ Przewodnika po stylu Google C ++ ”)

Dla parametru funkcji przekazanego przez wartość, const nie ma wpływu na obiekt wywołujący, dlatego nie jest zalecane w deklaracjach funkcji. Zobacz TotW # 109 .

Używanie const w zmiennych lokalnych nie jest ani zalecane, ani odradzane.

Źródło: sekcja „Korzystanie z const” Przewodnika po stylu Google C ++: https://google.github.io/styleguide/cppguide.html#Use_of_const . To jest naprawdę bardzo cenna sekcja, więc przeczytaj całą sekcję.

Zauważ, że „TotW # 109” oznacza „Wskazówka tygodnia nr 109: znacząca constw deklaracjach funkcji” , i jest również użyteczną lekturą. Jest bardziej informacyjny i mniej nakazowy co do tego, co zrobić, i na podstawie kontekstu pojawił się przed zasadą przewodnika po stylu Google C ++ constcytowaną tuż powyżej, ale ze względu na jej przejrzystość, constzasada cytowana powyżej została dodana do Google C ++ Przewodnik po stylu.

Zauważ też, że chociaż cytuję tutaj Przewodnik po stylu Google C ++ w obronie mojej pozycji, to wcale NIE oznacza, że ​​zawsze postępuję zgodnie z tym przewodnikiem lub zawsze zalecam stosowanie się do niego. Niektóre rzeczy zalecają są po prostu dziwne, takie jak ich kDaysInAWeek-Style konwencji nazewnictwa dla „Constant Names” . Jednak nadal przydatne i istotne jest wskazanie, kiedy jedna z najbardziej udanych i wpływowych firm technicznych i programistycznych na świecie stosuje takie samo uzasadnienie, jak ja i inni, tacy jak @Adisak, aby poprzeć nasze poglądy na ten temat.

4. Liner Clanga clang-tidy, ma kilka opcji:

A. Warto również zauważyć, że linter dzyń, w clang-tidy, ma opcję, readability-avoid-const-params-in-decls, opisane tutaj , aby wspierać egzekwowanie w bazie kodu nie używając constdo parametrów funkcji pass-by-value :

Sprawdza, czy deklaracja funkcji ma parametry, które są const najwyższego poziomu.

Stałe wartości w deklaracjach nie wpływają na podpis funkcji, dlatego nie należy ich tam umieszczać.

Przykłady:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

A oto dwa kolejne przykłady, które dodam siebie dla kompletności i jasności:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B. Ma również tę opcję: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. Moje pragmatyczne podejście do tego, jak sformułowałem przewodnik po stylu w tej sprawie:

Po prostu skopiuję i wkleję to do mojego przewodnika po stylach:

[KOPIUJ / WKLEJ START]

  1. Zawsze używaj const parametrów funkcji przekazywanych przez referencję lub wskaźnik, gdy ich zawartość (na co wskazują) NIE ma być zmieniana. W ten sposób staje się oczywiste, gdy zmienna przekazana przez referencję lub wskaźnik JEST oczekiwana do zmiany, ponieważ będzie jej brakować const. W tym przypadku użyciaconst zapobiega przypadkowym skutkom ubocznym poza funkcją.
  2. Nie zaleca się używania constparametrów funkcji przekazywanych przez wartość, ponieważ constnie ma to wpływu na obiekt wywołujący: nawet jeśli zmienna zostanie zmieniona w funkcji, nie będzie żadnych efektów ubocznych poza funkcją. Zobacz następujące zasoby, aby uzyskać dodatkowe uzasadnienie i wgląd:
    1. „Przewodnik po stylu Google C ++” „Sekcja„ Const ”
    2. „Wskazówka tygodnia nr 109: znaczące constdeklaracje funkcji”
    3. Odpowiedź przepełnienia stosu Adisaka na „Użycie„ const ”dla parametrów funkcji”
  3. Nigdy nie używaj najwyższego poziomu const[tj. constParametrów przekazywanych przez wartość ] do parametrów funkcji w deklaracjach, które nie są definicjami (i uważaj, aby nie kopiować / wklejać nic nie znaczącego const). Jest to bez znaczenia i ignorowane przez kompilator, jest to szum wizualny , i może wprowadzać czytelników w błąd ”( https://abseil.io/tips/109 , podkreślenie dodane).
    1. Jedynymi constkwalifikatorami mającymi wpływ na kompilację są te umieszczone w definicji funkcji, NIE te w deklaracji forward funkcji, takiej jak deklaracja funkcji (metody) w pliku nagłówkowym.
  4. Nigdy nie używaj najwyższego poziomu const[tj. Dla constzmiennych przekazywanych przez wartość ] do wartości zwracanych przez funkcję.
  5. Wykorzystanie constwskaźników lub referencji zwróconych przez funkcję zależy od implementatora , ponieważ czasami jest to przydatne.
  6. DO ZROBIENIA: egzekwuj niektóre z powyższych dzięki następującym clang-tidyopcjom:
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

Oto kilka przykładów kodu pokazujących constreguły opisane powyżej:

constPrzykłady parametrów:
(niektóre są stąd zapożyczone )

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

constPrzykłady typu zwrotu:
(niektóre są stąd zapożyczone )

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[KOPIUJ / WKLEJ]


2

W przypadku, o którym wspominasz, nie wpływa to na osoby wywołujące interfejs API, dlatego nie jest to często wykonywane (i nie jest konieczne w nagłówku). Wpływa tylko na implementację twojej funkcji.

Nie jest to szczególnie zła rzecz do zrobienia, ale korzyści nie są tak świetne, biorąc pod uwagę, że nie wpływa to na interfejs API i dodaje pisanie, więc zwykle nie jest to zrobione.


2

Nie używam const dla parametru przekazywanego przez wartość. Dzwoniący nie dba o to, czy zmienisz parametr, czy nie, jest to szczegół implementacji.

Naprawdę ważne jest oznaczenie metod jako const, jeśli nie modyfikują one swojego wystąpienia. Rób to w miarę postępów, ponieważ w przeciwnym razie możesz skończyć z dużą ilością const_cast <> lub może się okazać, że oznaczenie metody const wymaga zmiany dużej ilości kodu, ponieważ wywołuje inne metody, które powinny być oznaczone const.

Zazwyczaj zaznaczam lokalne zmienne war, jeśli nie muszę ich modyfikować. Uważam, że ułatwia to zrozumienie kodu, ułatwiając identyfikację „ruchomych części”.



2

Używam const, gdybym mógł. Stała dla parametrów oznacza, że ​​nie powinny zmieniać swojej wartości. Jest to szczególnie cenne przy przekazywaniu przez odniesienie. const dla funkcji deklaruje, że funkcja nie powinna zmieniać członków klas.


2

Podsumowując:

  • „Zwykle stała wartość dodana jest nieprzydatna i co najmniej wprowadzająca w błąd”. Z GOTW006
  • Ale możesz dodać je do pliku .cpp, tak jak w przypadku zmiennych.
  • Zauważ, że standardowa biblioteka nie używa const. Np std::vector::at(size_type pos). To, co wystarcza dla standardowej biblioteki, jest dla mnie dobre.

2
„To, co wystarcza dla standardowej biblioteki, jest dla mnie dobre” nie zawsze jest prawdą. Na przykład standardowa biblioteka używa brzydkich nazw zmiennych jak przez _Tmpcały czas - nie chcesz tego (w rzeczywistości nie możesz ich używać).
anatolyg

1
@anatolyg to szczegół implementacji
Fernando Pelliccioni,

2
OK, zarówno nazwy zmiennych, jak i typy const-kwalifikowane na listach argumentów są szczegółami implementacji. Chcę powiedzieć, że implementacja standardowej biblioteki czasami nie jest dobra. Czasami możesz (i powinieneś) lepiej. Kiedy powstał kod standardowej biblioteki - 10 lat temu? 5 lat temu (niektóre jego najnowsze części)? Możemy dziś napisać lepszy kod.
anatolyg

1

Jeśli parametr jest przekazywany przez wartość (i nie jest referencją), zwykle nie ma dużej różnicy, czy parametr jest zadeklarowany jako const, czy nie (chyba że zawiera element referencyjny - nie jest to problem dla typów wbudowanych). Jeśli parametr jest odniesieniem lub wskaźnikiem, zwykle lepiej jest chronić pamięć odniesienia / wskazaną do pamięci, a nie sam wskaźnik (myślę, że nie możesz ustawić samego odniesienia, nie ma to znaczenia, ponieważ nie możesz zmienić sędziego) . Dobrym pomysłem jest ochrona wszystkiego, co możesz jako const. Możesz go pominąć bez obawy, że popełnisz błąd, jeśli parametry to tylko POD (w tym typy wbudowane) i nie ma szans na ich dalszą zmianę na drodze (np. W twoim przypadku parametr bool).

Nie wiedziałem o różnicy deklaracji pliku .h / .cpp, ale ma to jakiś sens. Na poziomie kodu maszynowego nic nie jest „const”, więc jeśli zadeklarujesz funkcję (w .h) jako non-const, kod będzie taki sam, jakbyś zadeklarował ją jako const (pomijając optymalizacje). Pomaga to jednak zapisać kompilatorowi, że nie zmienisz wartości zmiennej wewnątrz implementacji funkcji (.ccp). Może się to przydać w przypadku dziedziczenia po interfejsie, który umożliwia zmianę, ale nie trzeba zmieniać parametru, aby osiągnąć wymaganą funkcjonalność.


0

Nie nakładałbym const na takie parametry - wszyscy już wiedzą, że wartość logiczna (w przeciwieństwie do wartości logicznej i) jest stała, więc dodanie jej sprawi, że ludzie pomyślą „czekaj, co?” a nawet że przekazujesz parametr przez odniesienie.


4
czasem chcesz przekazać obiekt przez referencję (ze względów wydajnościowych), ale go nie zmieniać, więc const jest wtedy obowiązkowy. Zachowanie wszystkich takich parametrów - nawet boolów - const byłoby dobrą praktyką, ułatwiając czytanie kodu.
gbjbaanb

0

rzeczą, o której należy pamiętać w const, jest to, że o wiele łatwiej jest tworzyć rzeczy const od samego początku, niż próbować włożyć je później.

Użyj const, jeśli chcesz, aby coś się nie zmieniło - to dodatkowa wskazówka, która opisuje, co robi twoja funkcja i czego się spodziewać. Widziałem wiele C API, które mogłyby zrobić z niektórymi z nich, szczególnie tymi, które akceptują ciągi c!

Byłbym bardziej skłonny do pominięcia słowa kluczowego const w pliku cpp niż w nagłówku, ale ponieważ mam tendencję do wycinania i wklejania ich, byłyby one przechowywane w obu miejscach. Nie mam pojęcia, dlaczego kompilator na to pozwala, wydaje mi się, że jest to kompilator. Najlepszą praktyką jest zdecydowanie umieszczenie słowa kluczowego const w obu plikach.


W ogóle tego nie rozumiem. Dlaczego miałbyś skłonny pominąć go w pliku CPP (definicja funkcji)? Tam właśnie to coś znaczy i może wyłapać błędy. Jak myślisz, dlaczego najlepszą praktyką jest umieszczanie const w obu miejscach? W pliku nagłówkowym (deklaracja funkcji) to nic nie znaczy i zaśmieca interfejs API. Być może niewielka wartość ma to, że deklowanie i defn wyglądają dokładnie tak samo, ale wydaje mi się, że to naprawdę niewielka korzyść w porównaniu z kwestią zaśmiecania interfejsu API.
Don Hatch,

@DonHatch 8 lat później, wow. W każdym razie, jak powiedział PO „Byłem również zaskoczony, gdy dowiedziałem się, że można pominąć const w parametrach w deklaracji funkcji, ale można to uwzględnić w definicji funkcji”.
gbjbaanb

0

Naprawdę nie ma powodu, aby tworzyć parametr-wartość „const”, ponieważ funkcja może i tak modyfikować tylko kopię zmiennej.

Powodem użycia „const” jest podanie przez odniesienie czegoś większego (np. Struktury z dużą liczbą elementów), w którym to przypadku zapewnia, że ​​funkcja nie może go zmodyfikować; a raczej kompilator narzeka, jeśli spróbujesz go zmodyfikować w tradycyjny sposób. Zapobiega to przypadkowej modyfikacji.


0

Stały parametr jest użyteczny tylko wtedy, gdy parametr jest przekazywany przez referencję, tj. Referencję lub wskaźnik. Gdy kompilator zobaczy parametr const, upewnij się, że zmienna użyta w parametrze nie jest modyfikowana w treści funkcji. Dlaczego ktoś miałby chcieć ustawić parametr wartości według wartości jako stały? :-)


Z wielu powodów. Określenie parametru według wartości constjasno mówi: „Nie muszę tego zmieniać, więc to deklaruję. Jeśli spróbuję go później zmodyfikować, daj mi błąd czasu kompilacji, abym mógł naprawić swój błąd lub odznaczyć go jako const”. To kwestia higieny i bezpieczeństwa kodu. Wszystko, czego potrzeba, aby dodać do plików implementacyjnych, powinno być czymś, co ludzie robią jako czysty refleks, IMO.
underscore_d

0

Ponieważ parametry są przekazywane przez wartość, nie ma znaczenia, czy podasz const, czy nie z perspektywy funkcji wywołującej. Zasadniczo nie ma sensu deklarować parametrów pass by value jako const.


0

Wszystkie consts w twoich przykładach nie mają celu. C ++ jest domyślnie przekazywany przez wartość, więc funkcja pobiera kopie tych liczb całkowitych i logicznych. Nawet jeśli funkcja je zmodyfikuje, nie wpłynie to na kopię dzwoniącego.

Więc unikałbym dodatkowych stałych, ponieważ

  • Są redudantami
  • Zaśmiecają tekst
  • Uniemożliwiają mi zmianę przekazywanej wartości w przypadkach, w których może być przydatna lub wydajna.

-1

Wiem, że pytanie jest „trochę” nieaktualne, ale kiedy do niego dotarłem, ktoś inny może to zrobić w przyszłości ... ... wciąż wątpię, żeby biedny człowiek tu listy, aby przeczytać mój komentarz :)

Wydaje mi się, że wciąż jesteśmy zbyt ograniczeni do myślenia w stylu C. W paradygmacie OOP bawimy się przedmiotami, a nie typami. Obiekt Const może być koncepcyjnie różny od obiektu non-const, szczególnie w sensie const logical (w przeciwieństwie do const logicznego). Zatem nawet jeśli stała poprawność parametrów funkcji jest (być może) nadmierną ostrożnością w przypadku POD, nie jest tak w przypadku obiektów. Jeśli funkcja działa z obiektem const, powinna to powiedzieć. Rozważ następujący fragment kodu

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

ps .: możesz argumentować, że odniesienie (const) byłoby bardziej odpowiednie tutaj i daje ci to samo zachowanie. Racja. Daję inny obraz niż to, co widziałem gdzie indziej ...

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.