Const przed czy const po?


145

Na początek prawdopodobnie wiesz, że constmożna to wykorzystać do uczynienia danych obiektu lub wskaźnika niemodyfikowalnymi lub obu.

const Object* obj; // can't change data
Object* const obj; // can't change pointer
const Object* const obj; // can't change data or pointer

Możesz jednak również użyć składni:

Object const *obj; // same as const Object* obj;

Jedyne, co wydaje się mieć znaczenie, to po której stronie gwiazdki umieścisz constsłowo kluczowe. Osobiście wolę umieścić constpo lewej stronie typu, aby określić, że dane nie są modyfikowalne, ponieważ uważam, że lepiej czyta się w moim nastawieniu od lewej do prawej, ale która składnia była pierwsza?

Co ważniejsze, dlaczego istnieją dwa prawidłowe sposoby określania constdanych i w jakiej sytuacji wolałbyś lub potrzebowałbyś jednego nad drugim, jeśli w ogóle?

Edytować:

Wygląda więc na to, że była to arbitralna decyzja, kiedy standard dotyczący tego, jak kompilatorzy powinni interpretować rzeczy, został opracowany na długo przed moim urodzeniem. Ponieważ constjest stosowane do tego, co znajduje się po lewej stronie słowa kluczowego (domyślnie?), Domyślam się, że nie było nic złego w dodaniu „skrótów” do stosowania słów kluczowych i kwalifikatorów typów w inny sposób, przynajmniej do czasu, gdy deklaracja zmieni się o analizowanie * lub & ...

Tak było również w C, więc zakładam?


9
W makrach zawsze dodawaj const po typie, np. #define MAKE_CONST(T) T constZamiast #define MAKE_CONST(T) const Ttak, MAKE_CONST(int *)aby poprawnie rozwinął się do int * constzamiast const int *.
jotik

6
Widziałem te dwa style określane jako „wschodnia konst” i „zachodnia konst.”.
Tom Anderson,

13
@TomAnderson, ale tak naprawdę powinno to być „east const” i „const west”.
YSC

Odpowiedzi:


96

dlaczego istnieją dwa poprawne sposoby określania constdanych i w jakiej sytuacji wolisz lub potrzebujesz jednego nad drugim, jeśli w ogóle?

Zasadniczo powodem, dla którego pozycja constwewnątrz specyfikatorów przed gwiazdką nie ma znaczenia, jest to, że gramatyka języka C została zdefiniowana w ten sposób przez Kernighana i Ritchiego.

Powodem, dla którego zdefiniowali gramatykę w ten sposób, było prawdopodobne, że ich kompilator C przeanalizował dane wejściowe od lewej do prawej i zakończył przetwarzanie każdego tokenu, gdy je zużywał. Użycie *tokenu zmienia stan bieżącej deklaracji na typ wskaźnika. Spotkanieconst po *oznacza, że constkwalifikator jest stosowany do deklaracji wskaźnika; napotykając go przed *środkami, kwalifikator jest stosowany do wskazanych danych.

Ponieważ znaczenie semantyczne nie zmienia się, jeśli constkwalifikator pojawia się przed lub po specyfikatorach typu, jest akceptowany w obu przypadkach.

Podobny przypadek pojawia się przy deklarowaniu wskaźników do funkcji, gdzie:

  • void * function1(void)deklaruje funkcję, która zwraca void *,

  • void (* function2)(void)deklaruje wskaźnik funkcji do funkcji, która zwraca void.

Znowu należy zauważyć, że składnia języka obsługuje parser od lewej do prawej.


7
Kernighan jest współautorem książki, ale nie był zaangażowany w projekt C, tylko Ritchie.
Tom Zych,

8
Nigdy nie mogłem sobie przypomnieć, który jest który. Dzięki twojemu wyjaśnieniu w końcu mam pamięć mnemotechniczną. Dzięki! Zanim *parser kompilatora nie wie, że jest wskaźnikiem, jest to stała dla wartości danych. Po * jest to związane ze stałym wskaźnikiem. Znakomity. I wreszcie wyjaśnia, dlaczego mogę zrobić const charrównie dobrze char const.
Vit Bernatik,

2
Zgadywanie, dlaczego tak się stało, wydaje mi się raczej słabe / wewnętrznie sprzeczne. To znaczy, gdybym definiował język i pisał kompilator i chciałbym zachować prostotę i „przeanalizować dane wejściowe od lewej do prawej i zakończyć przetwarzanie każdego tokenu w miarę jego wykorzystywania”, jak mówisz, wydaje mi się Wymagałbym, constaby zawsze pojawiał się po rzeczy, do której się kwalifikuje ... dokładnie tak, abym zawsze mógł zakończyć przetwarzanie const natychmiast po jej spożyciu. Wydaje się więc, że jest to argument przemawiający za zakazem zachodu, zamiast na to zezwalać.
Don Hatch

3
„Ponieważ znaczenie semantyczne nie zmienia się, jeśli kwalifikator const pojawia się przed lub po specyfikatorach typu, jest akceptowany w obu przypadkach”. Czy to nie jest okrężne rozumowanie? Pytanie brzmi, dlaczego znaczenie semantyczne jest tak zdefiniowane, więc nie sądzę, że to zdanie coś wnosi.
Don Hatch,

1
@donhatch Musisz pamiętać, że w stosunku do dnia dzisiejszego i założeń, które poczyniliśmy na podstawie naszej znajomości dobrego projektu języka programowania, języki były wtedy całkiem nowe. Również to, czy ktoś posługuje się językiem liberalnym, czy ograniczonym, jest oceną wartościującą. Np. Czy Python powinien mieć ++operator? „To zdanie”, imho, pomogło mi zrozumieć, że nie ma żadnego konkretnego powodu poza tym, że mogli. Może dziś dokonaliby innego wyboru, a może nie.
ragerdl

75

Zasada jest taka:

const dotyczy tego, co z niego zostało. Jeśli nic nie znajduje się po lewej stronie, dotyczy to rzeczy po prawej stronie.

Wolę używać const po prawej stronie rzeczy, aby być const tylko dlatego, że jest to „oryginalny” sposób definiowania const.

Myślę jednak, że jest to bardzo subiektywny punkt widzenia.


18
Wolę umieścić go po lewej stronie, ale myślę, że umieszczenie go po prawej ma więcej sensu. Zazwyczaj czytasz typy w C ++ od prawej do lewej, na przykład Object const *jest wskaźnikiem do stałej Object. Jeśli umieścisz constpo lewej stronie, odczytałby on jako wskaźnik do obiektu, który jest stałą, która tak naprawdę nie płynie zbyt dobrze.
Collin Dauphinee

1
Mam wrażenie, że po lewej stronie jest dla spójności w ludzkim stylu z innymi typami deklaracji C (komputerowo nie jest to poprawne, ponieważ constnie jest to klasa pamięci, ale ludzie nie są parserami).
geekozaur

1
@Heath Uważam, że jest to bardziej wskazówka niż reguła i często słyszałem to jako sposób na zapamiętanie, jak kompilator to zinterpretuje ... Rozumiem, jak to działa, więc byłem tylko ciekawy procesu myślowego stojącego za decyzja o wsparciu obu stron.
AJG85

3
@HeathHunnicutt reguła istnieje, ale jest trochę bardziej skomplikowana: c-faq.com/decl/spiral.anderson.html
imallett

2
@HeathHunnicutt: reguła spiralna jest rozszerzoną wersją komentarza pierwszego komentatora „Generalnie czytasz typy w [C /] C ++ od prawej do lewej”. Przypuszczałem, że temu zaprzeczasz. Myślę jednak, że zamiast tego odnosiłeś się do samej odpowiedzi.
imallett

57

Wolę drugą składnię. Pomaga mi śledzić to, co jest stałe, czytając deklarację typu od prawej do lewej:

Object * const obj;        // read right-to-left:  const pointer to Object
Object const * obj;        // read right-to-left:  pointer to const Object
Object const * const obj;  // read right-to-left:  const pointer to const Object

3
Dokładnie. A „stały wskaźnik do stałego obiektu” jest Object const* const, nie const const Object*. „const” nie może znajdować się po lewej stronie, z wyjątkiem szczególnych przypadków, w których tak wielu ludzi absolutnie je uwielbia. (Zobacz Heath powyżej.)
cdunn2001

38

Kolejność słów kluczowych w deklaracji nie jest ustalona. Istnieje wiele alternatyw dla „jedynego prawdziwego porządku”. Lubię to

int long const long unsigned volatile i = 0;

czy powinno być

volatile unsigned long long int const i = 0;

??


25
+1 za całkowicie mylącą definicję prostej zmiennej. :)
Xeo

@rubenvb - Tak, niestety tak. Gramatyka mówi tylko, że a decl-specifier-seqjest sekwencją decl-specifiers. W gramatyce nie ma kolejności, a liczba wystąpień każdego słowa kluczowego jest ograniczona tylko pewnymi regułami semantycznymi (możesz mieć jedną, constale dwie long:-)
Bo Persson

3
@rubenvb - Tak, unsignedto typ, taki sam jak unsigned inti int unsigned. unsigned longto inny typ, taki sam jak unsigned long inti int long unsigned. Widzisz wzór?
Bo Persson

2
@Bo: Widzę bałagan, muszę mieć trzy, żeby zobaczyć wzór ;). OK, dzięki
rubenvb

1
Kiedyś staticmogłeś dodawać do tej gmatwaniny słów, ale dopiero niedawno kompilatorzy narzekali, że staticmusi to być pierwsze.
Mark Lakata

8

Pierwszą zasadą jest użycie dowolnego formatu wymaganego przez lokalne standardy kodowania. Po tym: umieszczenie początku constna początku prowadzi do niekończącego się zamieszania, gdy w grę wchodzą kroje pisma, np .:

typedef int* IntPtr;
const IntPtr p1;   // same as int* const p1;

Jeśli twój standard kodowania zezwala na typedef wskaźników, to naprawdę powinien nalegać na umieszczenie stałej po typie. W każdym przypadku, ale w przypadku zastosowania do typu, const musi być zgodne z tym, do czego się odnosi, więc spójność również przemawia na korzyść stałej po. Ale lokalne wytyczne dotyczące kodowania mają przewagę nad wszystkimi tymi; różnica zwykle nie jest na tyle ważna, aby wrócić i zmienić cały istniejący kod.


Myślę, że może to uwydatnić powód, dla którego nie mamy czcionek wskaźników w naszych raczej luźno zdefiniowanych standardach w tym sklepie.
AJG85

1
Moją polityką (kiedy mogę decydować sam) jest umieszczenie stałej po (dla zachowania spójności) i nie używanie czcionek typu do wskaźników (lub ogólnie rzecz biorąc typedef) :-). A tak przy okazji, string :: iterator vs. string :: const_iterator prawdopodobnie również powinny zostać uwzględnione w Twojej decyzji. (Żeby zmylić rzeczy :-). Nie ma właściwej odpowiedzi.)
James Kanze

Ach tak, mogłem uwzględnić również zachowanie const std::string::const_iterator;)
AJG85

2
@JamesKanze - Poczekaj chwilę, pomóż mi ... Nie widzę zamieszania w opublikowanym przykładzie. Co jeszcze mogłoby const IntPtr p1oznaczać inne niż „stały wskaźnik liczby całkowitej” (tj. „Stały wskaźnik do liczby całkowitej”)? Nikt przy zdrowych zmysłach, nawet nie wiedząc, jak IntPtrjest zdefiniowane, nie pomyślałby, że p1jest to zmienne. A jeśli o to chodzi, dlaczego ktoś miałby błędnie zakładać, że *p1jest to niezmienne? Co więcej, umieszczenie const gdziekolwiek indziej (np. IntPtr const p1) W ogóle nie zmienia semantyki.
Todd Lehman,

3
@ToddLehman Możesz nie widzieć zamieszania, ale większość programistów C ++ to robi i systematycznie robi to źle (bez wątpienia pomaga w takich sytuacjach std::vector<T>::const_iterator, w których to nie iterator jest stałą, ale wskazuje).
James Kanze,

7

Istnieją historyczne powody, dla których lewica lub prawica są dopuszczalne. Stroustrup dodał const do C ++ w 1983 roku , ale nie dotarł do C aż do C89 / C90.

W C ++ jest dobry powód, aby zawsze używać const po prawej stronie. Będziesz spójny wszędzie, ponieważ funkcje składowe const muszą być deklarowane w ten sposób:

int getInt() const;

1
… cóż, „dobry powód” nie jest zbyt przekonujący, ponieważ inne możliwe lokalizacje „const” nie oznaczałyby tego samego. const int& getInt(); int& const getInt();
Maestro

1
@Maestro: Sugeruję, że int const& getInt();jest lepszy niż odpowiednik, const int& getInt();podczas gdy int& const getInt();to, z którym go porównujesz, jest zbędny (odniesienia są już stałe), chociaż legalne i zwykle daje ostrzeżenie. W każdym razie const na funkcji składowej zmienia thiswskaźnik w funkcji z Foo* constna Foo const* const.
Nick Westgate

constfunkcja składowa nie oznacza wcale tego samego - czy też jest void set(int)&;jakimś odniesieniem do funkcji?
Davis Herring
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.