Jakie są zasady dotyczące używania podkreślenia w identyfikatorze C ++?

930

W C ++ powszechne jest nazywanie zmiennych składowych z pewnym prefiksem, który oznacza, że ​​są to zmienne składowe, a nie zmienne lokalne lub parametry. Jeśli pochodzisz z tła MFC, prawdopodobnie będziesz używać m_foo. Od myFooczasu do czasu widziałem .

Wydaje się, że C # (lub po prostu .NET) zaleca używanie tylko podkreślenia, jak w _foo. Czy jest to dozwolone przez standard C ++?

Roger Lipscombe
źródło
3
Strona podręcznika glibc na ten temat znajduje się na stronie gnu.org/software/libc/manual/html_node/Reserved-Names.html Edycja: patrz również opengroup.org/onlinepubs/009695399/functions/xsh_chap02_02.html
CesarB
6
Wystarczy zauważyć, że nieznajomość tych reguł niekoniecznie oznacza, że ​​Twój kod nie będzie się kompilować ani uruchamiać, ale jest prawdopodobne, że Twój kod nie będzie przenośny dla różnych kompilatorów i wersji, ponieważ nie można zagwarantować, że nie będzie nazwy starcia. Aby to zrobić, wiem o pewnej implementacji ważnego systemu, który wszędzie używany jest jako konwencja nazewnictwa. Tam nie ma z tego powodu błędów. Oczywiście jest to zła praktyka.
g24l

Odpowiedzi:

852

Reguły (które nie zmieniły się w C ++ 11):

  • Zarezerwowane w dowolnym zakresie, w tym do wykorzystania jako makra implementacyjne :
    • identyfikatory zaczynające się od znaku podkreślenia, po którym bezpośrednio następuje wielka litera
    • identyfikatory zawierające sąsiadujące podkreślenia (lub „podwójne podkreślenie”)
  • Zarezerwowane w globalnej przestrzeni nazw:
    • identyfikatory rozpoczynające się znakiem podkreślenia
  • Ponadto wszystko w stdprzestrzeni nazw jest zarezerwowane. (Możesz jednak dodawać specjalizacje do szablonów).

Od standardu C ++ 2003:

17.4.3.1.2 Nazwy globalne [lib.global.names]

Niektóre zestawy nazw i sygnatur funkcji są zawsze zarezerwowane dla implementacji:

  • Każda nazwa, która zawiera podwójny znak podkreślenia ( __) lub zaczyna się od znaku podkreślenia, po którym następuje wielka litera (2.11), jest zarezerwowana dla implementacji do dowolnego użytku.
  • Każda nazwa rozpoczynająca się znakiem podkreślenia jest zarezerwowana dla implementacji i może być używana jako nazwa w globalnej przestrzeni nazw. 165

165) Takie nazwy są również zastrzeżone w przestrzeni nazw ::std(17.4.3.1).

Ponieważ C ++ jest oparty na standardzie C (1.1 / 2, C ++ 03), a C99 jest odniesieniem normatywnym (1.2 / 1, C ++ 03), mają one również zastosowanie, od standardu C z 1999 roku:

7.1.3 Zastrzeżone identyfikatory

Każdy nagłówek deklaruje lub definiuje wszystkie identyfikatory wymienione w powiązanej podklasie i opcjonalnie deklaruje lub definiuje identyfikatory wymienione w powiązanej podklasie i identyfikatorach przyszłych bibliotek, które są zawsze zarezerwowane do dowolnego użytku lub do wykorzystania jako identyfikatory zakresu plików.

  • Wszystkie identyfikatory rozpoczynające się znakiem podkreślenia i wielką literą lub innym znakiem podkreślenia są zawsze zarezerwowane do dowolnego użytku.
  • Wszystkie identyfikatory rozpoczynające się znakiem podkreślenia są zawsze zarezerwowane do stosowania jako identyfikatory o zakresie pliku zarówno w zwykłej, jak i w przestrzeni nazw.
  • Każda nazwa makra w dowolnym z poniższych podrozdziałów (łącznie z przyszłymi kierunkami biblioteki) jest zarezerwowana do użytku, tak jak określono, jeśli uwzględniono którykolwiek z powiązanych z nią nagłówków; chyba że wyraźnie zaznaczono inaczej (patrz 7.1.4).
  • Wszystkie identyfikatory z zewnętrznym powiązaniem w dowolnym z poniższych podrozdziałów (w tym przyszłe kierunki bibliotek) są zawsze zarezerwowane do użycia jako identyfikatory z zewnętrznym łączeniem. 154
  • Każdy identyfikator z zakresem pliku wymienionym w którejkolwiek z poniższych podklas (w tym przyszłe kierunki bibliotek) jest zarezerwowany do użycia jako nazwa makra i jako identyfikator z zakresem pliku w tej samej przestrzeni nazw, jeśli uwzględniono którykolwiek z powiązanych z nim nagłówków.

Żadne inne identyfikatory nie są zastrzeżone. Jeśli program deklaruje lub definiuje identyfikator w kontekście, w którym jest zarezerwowany (inny niż dozwolony w 7.1.4), lub definiuje zastrzeżony identyfikator jako nazwę makra, zachowanie jest niezdefiniowane.

Jeśli program usunie (z #undef) dowolną definicją makra identyfikatora w pierwszej grupie wymienionej powyżej, zachowanie jest niezdefiniowane.

154) Lista zarezerwowanych identyfikatorów z zewnętrznym wiązaniem obejmuje errno, math_errhandling, setjmp, i va_end.

Mogą obowiązywać inne ograniczenia. Na przykład standard POSIX rezerwuje wiele identyfikatorów, które prawdopodobnie pojawią się w normalnym kodzie:

  • Nazwy rozpoczynające się od dużej Elitery następują po cyfrach lub wielkich literach:
    • może być użyty do dodatkowych nazw kodów błędów.
  • Nazwy, które zaczynają się na jedną islub tonastępują po niej małą literą
    • może być wykorzystywany do dodatkowych funkcji testowania znaków i konwersji.
  • Nazwy rozpoczynające się od, LC_po których następuje wielka litera
    • może być użyty do dodatkowych makr określających atrybuty ustawień regionalnych.
  • Nazwy wszystkich istniejących funkcji matematycznych z przyrostkiem flub lsą zastrzeżone
    • dla odpowiednich funkcji, które działają odpowiednio na liczbach zmiennoprzecinkowych i długich podwójnych argumentach.
  • Nazwy zaczynające się SIGod dużej litery są zastrzeżone
    • dla dodatkowych nazw sygnałów.
  • Nazwy zaczynające się SIG_od dużej litery są zastrzeżone
    • dla dodatkowych akcji sygnalizacyjnych.
  • Nazwy rozpoczynające się od str, memlub wcspoprzedzające małą literę są zastrzeżone
    • dla dodatkowych funkcji łańcuchowych i tablicowych.
  • Nazwy rozpoczynające się od PRIlub SCNpo każdej małej litery lub Xsą zastrzeżone
    • dla makr dodatkowego specyfikatora formatu
  • Nazwy kończące się _tsą zastrzeżone
    • dla dodatkowych nazw typów.

Mimo że używanie tych nazw do własnych celów może nie powodować problemów, zwiększają one możliwość konfliktu z przyszłymi wersjami tego standardu.


Osobiście nie zaczynam identyfikatorów od podkreślników. Nowy dodatek do mojej zasady: nie używaj nigdzie podwójnych znaków podkreślenia, co jest łatwe, ponieważ rzadko używam podkreślenia.

Po przestudiowaniu tego artykułu nie kończę już moich identyfikatorów, _t ponieważ jest to zastrzeżone przez standard POSIX.

Zasada o każdym identyfikatorze kończącym się na _tbardzo mnie zaskoczyła. Myślę, że to standard POSIX (jeszcze nie jestem pewien) szukający wyjaśnień i oficjalnego rozdziału i wersetu. Ten pochodzi z GNU Libtool ręcznych , wymieniające zastrzeżonych nazw.

CesarB podał następujący link do zastrzeżonych symboli POSIX 2004 i zauważa, że ​​„można tam znaleźć wiele innych zastrzeżonych prefiksów i sufiksów”. Tutaj są zdefiniowane symbole zastrzeżone POSIX 2008 . Ograniczenia są nieco bardziej szczegółowe niż powyższe.

Roger Pate
źródło
14
Standard C ++ nie „importuje” C, prawda? Importują pewne nagłówki, ale nie język jako całość ani zasady nazewnictwa, o ile mi wiadomo. Ale tak, ten mnie też zaskoczył. Ale ponieważ jest to C, może mieć zastosowanie tylko do globalnej ns. Powinny być bezpieczne w użyciu 't klas wewnątrz jak czytam
jalf
27
Standard C ++ nie „importuje” standardu C. To odwołuje się C Standard. Wprowadzenie do biblioteki C ++ mówi: „Biblioteka udostępnia również funkcje Standardowej Biblioteki C”. Robi to, włączając nagłówki biblioteki C Standard z odpowiednimi zmianami, ale nie „importując” jej. Standard C ++ ma własny zestaw reguł opisujących zastrzeżone nazwy. Jeśli nazwa zarezerwowana w C powinna być zarezerwowana w C ++, to jest to miejsce, aby to powiedzieć. Ale standard C ++ tego nie mówi. Nie wierzę więc, że rzeczy zastrzeżone w C są zastrzeżone w C ++ - ale mogę się mylić.
Johannes Schaub - litb
8
Oto, co znalazłem na temat problemu „_t”: n1256 (C99 TC3) mówi: „Nazwy Typedef zaczynające się od int lub uint i kończące się na _t” są zastrzeżone. Myślę, że nadal pozwala to na używanie nazw takich jak „foo_t” - ale myślę, że są one następnie zastrzeżone przez POSIX.
Johannes Schaub - litb
59
Czyli „tolerancja” jest zarezerwowana przez POSIX, ponieważ zaczyna się od „do” + małej litery? Założę się, że dużo kodu łamie tę zasadę!
Sjoerd,
23
@LokiAstari, „ Standard C ++ jest zdefiniowany w kategoriach standardu C. W zasadzie mówi się, że C ++ to C z tymi różnicami i dodatkami. ” Bzdury! C ++ odwołuje się tylko do standardu C w [basic.fundamental] i bibliotece. Jeśli to, co mówisz, jest prawdą, to gdzie C ++ to mówi _Booli _Imaginarynie istnieje w C ++? Język C ++ jest zdefiniowany jawnie, nie w kategoriach „edycji” w C, w przeciwnym razie standard może być znacznie krótszy!
Jonathan Wakely
198

Zasady unikania kolizji nazw znajdują się zarówno w standardzie C ++ (patrz książka Stroustrupa), jak i wspomniane przez guru C ++ (Sutter itp.).

Reguła osobista

Ponieważ nie chciałem zajmować się sprawami i chciałem prostej zasady, zaprojektowałem osobistą, która jest jednocześnie prosta i poprawna:

Podczas nazywania symbolu unikniesz kolizji z bibliotekami kompilatora / systemu operacyjnego / standardowego, jeśli:

  • nigdy nie rozpoczynaj symbolu znakiem podkreślenia
  • nigdy nie wymieniaj symbolu z dwoma kolejnymi podkreśleniami w środku.

Oczywiście umieszczenie kodu w unikalnej przestrzeni nazw pomaga również uniknąć kolizji (ale nie chroni przed złymi makrami)

Kilka przykładów

(Używam makr, ponieważ bardziej zanieczyszczają kod symboli C / C ++, ale może to być wszystko od nazwy zmiennej do nazwy klasy)

#define _WRONG
#define __WRONG_AGAIN
#define RIGHT_
#define WRONG__WRONG
#define RIGHT_RIGHT
#define RIGHT_x_RIGHT

Wyciągi z wersji roboczej C ++ 0x

Z pliku n3242.pdf (oczekuję, że końcowy standardowy tekst będzie podobny):

17.6.3.3.2 Nazwy globalne [global.names]

Niektóre zestawy nazw i sygnatur funkcji są zawsze zarezerwowane dla implementacji:

- Każda nazwa, która zawiera podwójny znak podkreślenia _ _ lub zaczyna się znakiem podkreślenia, po którym następuje wielka litera (2.12), jest zarezerwowana dla implementacji do dowolnego użytku.

- Każda nazwa rozpoczynająca się znakiem podkreślenia jest zarezerwowana dla implementacji i może być używana jako nazwa w globalnej przestrzeni nazw.

Ale również:

17.6.3.3.5 Sufiksy literału zdefiniowane przez użytkownika [usrlit.suffix]

Dosłowne identyfikatory sufiksów, które nie zaczynają się od znaku podkreślenia są zarezerwowane do przyszłej standaryzacji.

Ta ostatnia klauzula jest myląca, chyba że weźmiesz pod uwagę, że nazwa zaczynająca się od jednego znaku podkreślenia, po której następuje mała litera, byłaby OK, gdyby nie została zdefiniowana w globalnej przestrzeni nazw ...

paercebal
źródło
9
@Meysam: __WRONG_AGAIN__zawiera dwa kolejne podkreślenia (dwa na początku i dwa na końcu), więc jest to niezgodne ze standardem.
paercebal
8
@ BЈовић: WRONG__WRONGzawiera dwa kolejne podkreślenia (dwa w środku), więc zgodnie ze standardem jest to niewłaściwe
paercebal
2
umieszczenie kodu w unikalnej przestrzeni nazw również pomaga uniknąć kolizji : ale to wciąż nie wystarczy, ponieważ identyfikator może kolidować ze słowem kluczowym niezależnie od zakresu (np. __attribute__dla GCC).
Ruslan
1
Dlaczego istnieje problem posiadania dwóch kolejnych znaków podkreślenia pośrodku zgodnie ze standardem? Zdefiniowane przez użytkownika przyrostki literału dotyczą wartości literałów takich jak 1234567Llub 4.0f; IIRC odnosi się to do ohttp: //en.cppreference.com/w/cpp/language/user_literal
Jason S
2
Why is there any problem of having two consecutive underscores in the middle according to the standard?Ponieważ standard mówi, że są zastrzeżone. To nie jest rada dotycząca dobrego lub złego stylu. To decyzja ze standardu. Dlaczego tak postanowili? Sądzę, że pierwsze kompilatory już wcześniej stosowały takie konwencje nieformalnie przed standaryzacją.
paercebal,
38

Z MSDN :

Użycie dwóch sekwencyjnych znaków podkreślenia (__) na początku identyfikatora lub pojedynczego wiodącego znaku podkreślenia, po którym następuje wielka litera, jest zarezerwowane dla implementacji C ++ we wszystkich zakresach. Należy unikać używania jednego wiodącego znaku podkreślenia, po którym następuje mała litera dla nazw z zakresem pliku, ze względu na możliwe konflikty z bieżącymi lub przyszłymi zastrzeżonymi identyfikatorami.

Oznacza to, że możesz użyć pojedynczego podkreślenia jako prefiksu zmiennej składowej, o ile po nim następuje mała litera.

Jest to najwyraźniej zaczerpnięte z sekcji 17.4.3.1.2 standardu C ++, ale nie mogę znaleźć oryginalnego źródła pełnego standardu online.

Zobacz także to pytanie .

Roger Lipscombe
źródło
2
Podobny tekst znalazłem w n3092.pdf (szkic standardu C ++ 0x) w sekcji: „17.6.3.3.2 Nazwy globalne”
paercebal
7
Co ciekawe, wydaje się, że jest to jedyna odpowiedź, która ma bezpośrednią, zwięzłą odpowiedź na pytanie.
hyde
9
@hyde: W rzeczywistości tak nie jest, ponieważ pomija regułę, aby nie mieć żadnych identyfikatorów z wiodącym podkreśleniem w globalnej przestrzeni nazw. Zobacz odpowiedź Rogera . Byłbym bardzo ostrożny w stosunku do cytowań dokumentów MS VC jako autorytetu w zakresie standardu C ++.
sbi
@sbi miałem na myśli „możesz użyć pojedynczego podkreślenia jako prefiksu zmiennej członka, o ile po nim następuje mała litera” w tej odpowiedzi, która odpowiada na pytanie w tekście pytania bezpośrednio i zwięźle, bez utonięcia w ścianie tekstu.
hyde,
5
Po pierwsze, nadal uważam brak jakiejkolwiek wskazówki, że ta sama reguła nie dotyczy globalnej przestrzeni nazw, za niepowodzenie. Co gorsza, przylegające podkreślenia są zabronione nie tylko na początku, ale w dowolnym miejscu identyfikatora. Tak więc ta odpowiedź nie tylko pomija fakt, ale faktycznie czyni co najmniej jedno czynnie błędne twierdzenie. Jak powiedziałem, odwoływanie się do dokumentów MSVC jest czymś, czego nie zrobiłbym, gdyby pytanie nie dotyczyło wyłącznie VC.
sbi
25

Jeśli chodzi o drugą część pytania, często umieszcza się podkreślenie na końcu nazwy zmiennej, aby nie kolidowało z niczym wewnętrznym.

Robię to nawet w klasach i przestrzeniach nazw, ponieważ muszę wtedy zapamiętać tylko jedną regułę (w porównaniu do „na końcu nazwy w zakresie globalnym i na początku nazwy wszędzie indziej”).

Max Lybbert
źródło
2

Tak, podkreślenia mogą być używane w dowolnym miejscu w identyfikatorze. Uważam, że są to następujące reguły: dowolne z az, AZ, _ w pierwszym znaku i te + 0-9 dla następujących znaków.

Prefiksy podkreślenia są powszechne w kodzie C - pojedynczy znak podkreślenia oznacza „prywatny”, a podwójne podkreślenia są zwykle zarezerwowane do użytku przez kompilator.

John Millikin
źródło
3
Są powszechne w bibliotekach. Nie powinny być powszechne w kodzie użytkownika.
Martin York,
43
Ludzie zrobić bibliotek zapisu w C, wiesz.
John Millikin,
7
„Tak, podkreślenia mogą być używane w dowolnym miejscu w identyfikatorze”. Jest to niewłaściwe w przypadku globalnych identyfikatorów. Zobacz odpowiedź Rogera .
sbi