Scott Meyers bardzo ładnie i dokładnie omawia ten temat. Jego pozycja nr 2 w „Effective C ++ Third Edition”. Dwa specjalne przypadki (1) stała statyczna jest preferowana w zakresie klas dla stałych specyficznych dla klasy; (2) Preferowana jest przestrzeń nazw lub stała zakresu anonimowego niż #define.
Eric
2
Wolę Enums. Ponieważ jest hybrydą obu. Nie zajmuje miejsca, chyba że utworzysz jego zmienną. Jeśli chcesz po prostu używać jako stałej, enum jest najlepszą opcją. Ma bezpieczeństwo typu w C / C ++ 11 std, a także doskonałą stałą. #define jest niebezpieczny dla typu, const zajmuje miejsce, jeśli kompilator nie może go zoptymalizować.
siddhusingh
1
Moja decyzja, czy użyć #definelub static const(w przypadku ciągów), zależy od aspektu inicjalizacji (nie zostało wspomniane w odpowiedziach poniżej): jeśli stała jest używana tylko w obrębie konkretnej jednostki kompilacji, to idę z static const, w przeciwnym razie używam #define- unikaj fiasku inicjalizacji kolejności statycznej isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak
Jeśli const, constexprlub enumjakakolwiek odmiana działa w twoim przypadku, to lepiej#define
Phil1970,
@MartinDvorak „ unikać fiasku inicjalizacji zamówienia statycznego ” Jak to problem dla stałych?
ciekawy
Odpowiedzi:
139
Osobiście nienawidzę preprocesora, więc zawsze się z tym zgadzam const.
Główną zaletą jest #defineto, że nie wymaga pamięci do przechowywania w twoim programie, ponieważ tak naprawdę po prostu zastępuje jakiś tekst literalną wartością. Ma również tę zaletę, że nie ma typu, więc można go użyć dla dowolnej liczby całkowitej bez generowania ostrzeżeń.
Zaletą „ const” jest to, że można je przeskalować i można ich użyć w sytuacjach, gdy konieczne jest przekazanie wskaźnika do obiektu.
Nie wiem dokładnie, do czego zmierzasz z częścią „ static”. Jeśli deklarujesz globalnie, umieściłbym go w anonimowej przestrzeni nazw zamiast używać static. Na przykład
namespace{unsignedconst seconds_per_minute =60;};int main (int argc;char*argv[]){...}
Stałe łańcuchowe są jedną z tych, #definektóre mogą odnieść korzyść z bycia , przynajmniej jeśli mogą być użyte jako „bloki konstrukcyjne” dla większych stałych łańcuchowych. Zobacz moją odpowiedź na przykład.
AnT
62
#defineZaletą nie stosując żadnej pamięci jest niedokładna. „60” w tym przykładzie musi być gdzieś zapisane, bez względu na to, static constczy jest lub #define. W rzeczywistości widziałem kompilatory, w których użycie #define powodowało ogromne zużycie pamięci (tylko do odczytu), a statyczny const nie używał niepotrzebnej pamięci.
Gilad Naor
3
#Define przypomina wpisanie go, więc na pewno nie pochodzi z pamięci.
Wielebny
27
@theReverend Czy dosłowne wartości są w jakiś sposób zwolnione z konsumpcji zasobów maszynowych? Nie, mogą po prostu używać ich na różne sposoby, może nie pojawią się na stosie lub sterty, ale w pewnym momencie program jest ładowany do pamięci wraz ze wszystkimi wartościami do niego skompilowanymi.
Sqeaky
13
@ gilad-naor, ogólnie masz rację, ale małe liczby całkowite, takie jak 60, mogą czasami być swego rodzaju częściowym wyjątkiem. Niektóre zestawy instrukcji mają możliwość kodowania liczb całkowitych lub podzbioru liczb całkowitych bezpośrednio w strumieniu instrukcji. Na przykład MIP dodają natychmiast ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). W takim przypadku można powiedzieć, że liczba całkowita #defined nie zajmuje miejsca, ponieważ w skompilowanym pliku binarnym zajmuje ona kilka zapasowych bitów instrukcji, które i tak musiały istnieć.
ahcox
242
Plusy i minusy między #defines, consts (co zapomniałeś) enums, w zależności od użycia:
enums:
możliwe tylko dla wartości całkowitych
dobrze skalowane / problemy z kolizją identyfikatorów są ładnie obsługiwane, szczególnie w klasach wyliczeniowych C ++ 11, w których wyliczenia enum class Xsą niejednoznaczne przez zakresX::
silnie wpisany, ale do wystarczająco dużego rozmiaru int podpisanego lub niepodpisanego, nad którym nie masz kontroli w C ++ 03 (chociaż możesz podać pole bitowe, w które powinny być spakowane, jeśli wyliczenie jest członkiem struktury / class / union), podczas gdy C ++ 11 jest domyślnie ustawiony, intale programista może go wyraźnie ustawić
nie można pobrać adresu - nie ma takiego, ponieważ wartości wyliczeń są skutecznie podstawiane w miejscu w punktach użycia
silniejsze ograniczenia użycia (np. inkrementacja - template <typename T> void f(T t) { cout << ++t; }nie kompiluje się, chociaż można zawijać wyliczenie do klasy za pomocą niejawnego konstruktora, operatora rzutowania i operatorów zdefiniowanych przez użytkownika)
typ każdej stałej wzięty z otaczającego wyliczenia, więc template <typename T> void f(T)uzyskaj odrębną instancję, gdy przekażesz tę samą wartość liczbową z różnych wyliczeń, z których wszystkie różnią się od rzeczywistej f(int)instancji. Kod obiektowy każdej funkcji może być identyczny (ignorując przesunięcia adresu), ale nie spodziewałbym się, że kompilator / linker wyeliminuje niepotrzebne kopie, chociaż możesz sprawdzić kompilator / linker, jeśli ci zależy.
nawet w przypadku typeof / decltype nie można oczekiwać, że wartości liczbowe zapewnią użyteczny wgląd w zestaw znaczących wartości i kombinacji (w rzeczywistości „legalne” kombinacje nie są nawet odnotowane w kodzie źródłowym, należy rozważyć enum { A = 1, B = 2 }- jest A|B„legalne” z logiki programu perspektywiczny?)
nazwa typu wyliczenia może pojawiać się w różnych miejscach w RTTI, komunikatach kompilatora itp. - być może przydatne, a może zaciemniające
nie można użyć wyliczenia bez faktycznego wyświetlenia wartości przez jednostkę tłumaczącą, co oznacza, że wyliczenia w interfejsach API bibliotek wymagają wartości ujawnionych w nagłówku, makea inne narzędzia do ponownej kompilacji oparte na znacznikach czasu uruchomią rekompilację klienta po ich zmianie (źle! )
consts:
poprawnie rozwiązano problemy z ładowaniem / konfliktami identyfikatorów
silny, pojedynczy, określony przez użytkownika
możesz spróbować „wpisać” #defineala #define S std::string("abc"), ale stała unika powtarzanej konstrukcji odrębnych elementów tymczasowych w każdym punkcie użytkowania
Komplikacje wynikające z reguły jednej definicji
może przyjmować adres, tworzyć stałe odniesienia do nich itp.
najbardziej podobny do non-const wartości, która minimalizuje pracę i wpływ przy przełączaniu między nimi
wartość może być umieszczona w pliku implementacji, umożliwiając zlokalizowaną rekompilację i tylko łącza klienta, aby odebrać zmianę
#defines:
zakres „globalny” / bardziej podatny na sprzeczne zastosowania, co może powodować trudne do rozwiązania problemy z kompilacją i nieoczekiwane wyniki w czasie wykonywania, a nie rozsądne komunikaty o błędach; złagodzenie tego wymaga:
długie, niejasne i / lub centralnie koordynowane identyfikatory, a dostęp do nich nie może korzystać z niejawnego dopasowywania używanej / bieżącej / sprawdzonej przez Koeniga przestrzeni nazw, aliasów przestrzeni nazw itp.
podczas gdy najlepsza praktyka sprawdzania wydajności pozwala, aby identyfikatory parametrów szablonu były jednoznakowymi dużymi literami (ewentualnie po nich cyfra), inne użycie identyfikatorów bez małych liter jest tradycyjnie zarezerwowane dla definicji preprocesora (poza biblioteką OS i C / C ++) nagłówki). Jest to ważne, aby korzystanie z preprocesora na skalę korporacyjną pozostało możliwe do zarządzania. Można oczekiwać, że biblioteki stron trzecich będą się spełniały. Obserwacja tego implikuje migrację istniejących stałych lub wyliczeń do / z definicji, wymaga zmiany wielkości liter, a zatem wymaga edycji kodu źródłowego klienta, a nie „prostej” ponownej kompilacji. (Osobiście wykorzystuję pierwszą literę wyliczeń, ale nie stałych, więc trafiłbym także migrując między nimi - może czas na przemyślenie tego.)
możliwe więcej operacji w czasie kompilacji: literalna łańcuchowa konkatenacja, łańcuchowa (biorąc pod uwagę jej rozmiar), konkatenacja w identyfikatory
Minusem jest to, że biorąc pod uwagę #define X "x", a niektóre Wykorzystanie klient ala "pre" X "post", jeśli chcesz lub musisz zrobić X runtime-zmienny zmienny niż stały wymusić zmiany w kodzie klienta (zamiast tylko rekompilacji), podczas gdy przejście jest łatwiejsze z punktu A const char*czy const std::stringdany oni już zmusza użytkownika do włączenia operacji konkatenacji (np. "pre" + X + "post"dla string)
nie można użyć sizeof bezpośrednio na zdefiniowanym literale liczbowym
untyped (GCC nie ostrzega w porównaniu do unsigned )
niektóre łańcuchy kompilatora / linkera / debuggera mogą nie przedstawiać identyfikatora, więc zredukujesz się do patrzenia na „magiczne liczby” (łańcuchy, cokolwiek ...)
nie mogę wziąć adresu
podstawiona wartość nie musi być zgodna z prawem (lub dyskretna) w kontekście, w którym tworzona jest #define, ponieważ jest ona oceniana w każdym punkcie użytkowania, aby można było odwoływać się do niezadeklarowanych obiektów, zależnie od „implementacji”, która nie musi bądź wstępnie dołączony, twórz „stałe”, takie jak te, { 1, 2 }które mogą być używane do inicjowania tablic #define MICROSECONDS *1E-6itp. ( zdecydowanie nie polecam tego!)
niektóre specjalne rzeczy, takie jak __FILE__i __LINE__mogą być włączone do podstawienia makr
możesz przetestować istnienie i wartość w #ifinstrukcjach pod kątem warunkowego włączenia kodu (mocniejszego niż postprocesing „jeśli”, ponieważ kod nie musi być kompilowany, jeśli nie zostanie wybrany przez preprocesor), użyj #undef-ine, redefine itp.
podstawiony tekst musi zostać ujawniony:
w jednostce tłumaczeniowej, z której korzysta, co oznacza, że makra w bibliotekach do użytku klienta muszą znajdować się w nagłówku, więc makeinne narzędzia do ponownej kompilacji oparte na znacznikach czasu uruchomią rekompilację klienta, gdy zostaną zmienione (źle!)
lub w wierszu poleceń, gdzie wymagana jest jeszcze większa ostrożność, aby upewnić się, że kod klienta został ponownie skompilowany (np. plik Makefile lub skrypt dostarczający definicję powinien być wymieniony jako zależność)
Moja osobista opinia:
Zasadniczo używam consts i uważam je za najbardziej profesjonalną opcję do ogólnego użytku (chociaż inne mają prostotę podobającą się do tego starego leniwego programisty).
Świetna odpowiedź. Jedna mała nitka: czasami używam lokalnych wyliczeń, które wcale nie są w nagłówkach, tylko dla jasności kodu, jak w małych automatach stanowych i tym podobnych. Dlatego nie muszą przez cały czas mieć nagłówków.
kert
Plusy i minusy są pomieszane, bardzo chciałbym zobaczyć tabelę porównawczą.
Nieznany 123
@ Nieznany123: nie krępuj się opublikować jednego - nie mam nic przeciwko, jeśli zerwiesz z tego punkty, które według ciebie są godne. Na zdrowie
Tony Delroy,
48
Jeśli jest to pytanie C ++ i wspomniane #definejako alternatywa, to dotyczy stałych „globalnych” (tj. Zakresu pliku), a nie członków klasy. Jeśli chodzi o takie stałe w C ++ static constjest zbędne. W C ++ constdomyślnie mają wewnętrzny link i nie ma sensu ich deklarować static. Więc tak naprawdę chodzi o constvs.#define .
I wreszcie, w C ++ constjest preferowane. Przynajmniej dlatego, że takie stałe są wpisywane i określane zasięgiem. Są po prostu nie ma powodów, aby wolą #defineponadconst , poza kilkoma wyjątkami.
Stałe łańcuchowe BTW są jednym z przykładów takiego wyjątku. Za pomocą #definestałych łańcucha d można użyć funkcji kompilacji w czasie kompilacji kompilatorów C / C ++, jak w
PS Znowu, na wszelki wypadek, gdy ktoś wymienia static constjako alternatywę #define, zwykle oznacza to, że mówi o C, a nie o C ++. Zastanawiam się, czy to pytanie jest poprawnie oznaczone ...
„ po prostu nie ma powodów, aby preferować #define ” nad czym? Zmienne statyczne zdefiniowane w pliku nagłówkowym?
ciekawy
9
#define może prowadzić do nieoczekiwanych wyników:
#include<iostream>#define x 500#define y x +5int z = y *2;int main(){
std::cout <<"y is "<< y;
std::cout <<"\nz is "<< z;}
Wysyła niepoprawny wynik:
y is505
z is510
Jeśli jednak zastąpisz to stałymi:
#include<iostream>constint x =500;constint y = x +5;int z = y *2;int main(){
std::cout <<"y is "<< y;
std::cout <<"\nz is "<< z;}
Wyprowadza poprawny wynik:
y is505
z is1010
Jest tak, ponieważ #definepo prostu zastępuje tekst. Ponieważ może to poważnie zepsuć kolejność operacji, zaleciłbym zamiast tego użycie zmiennej stałej.
Miałem inny nieoczekiwany wynik: ymiał wartość 5500, trochę endian konkatenacji xi 5.
Kody z Hammer
5
Używanie stałej statycznej jest jak używanie innych zmiennych const w kodzie. Oznacza to, że możesz śledzić, skąd pochodzą informacje, w przeciwieństwie do #define, które zostanie po prostu zastąpione w kodzie w procesie wstępnej kompilacji.
Stała statyczna jest wpisana (ma typ) i może być sprawdzona przez kompilator pod kątem ważności, redefinicji itp.
#define można zdefiniować na nowo niezdefiniowane cokolwiek.
Zwykle powinieneś preferować stałe statyczne. Nie ma wady. Prprocesor powinien być używany głównie do kompilacji warunkowej (a czasem do naprawdę brudnych trików).
Definiowanie stałych przy użyciu dyrektywy preprocesora #definenie jest zalecane do zastosowania nie tylko w C++, ale także w C. Te stałe nie będą miały typu. Nawet w Czaproponowano użycie constdla stałych.
Zawsze wolę używać funkcji języka niż niektórych dodatkowych narzędzi, takich jak preprocesor.
ES.31: Nie używaj makr dla stałych ani „funkcji”
Makra są głównym źródłem błędów. Makra nie przestrzegają zwykłych reguł zakresu i typów. Makra nie przestrzegają zwykłych zasad przekazywania argumentów. Makra zapewniają, że czytelnik widzi coś innego niż to, co widzi kompilator. Makra komplikują tworzenie narzędzi.
Jeśli definiujesz stałą, która ma być współużytkowana przez wszystkie instancje klasy, użyj stałej statycznej. Jeśli stała jest specyficzna dla każdej instancji, po prostu użyj const (ale zauważ, że wszystkie konstruktory klasy muszą zainicjować tę zmienną członka const na liście inicjalizacyjnej).
#define
lubstatic const
(w przypadku ciągów), zależy od aspektu inicjalizacji (nie zostało wspomniane w odpowiedziach poniżej): jeśli stała jest używana tylko w obrębie konkretnej jednostki kompilacji, to idę zstatic const
, w przeciwnym razie używam#define
- unikaj fiasku inicjalizacji kolejności statycznej isocpp.org/wiki/faq/ctors#static-init-orderconst
,constexpr
lubenum
jakakolwiek odmiana działa w twoim przypadku, to lepiej#define
Odpowiedzi:
Osobiście nienawidzę preprocesora, więc zawsze się z tym zgadzam
const
.Główną zaletą jest
#define
to, że nie wymaga pamięci do przechowywania w twoim programie, ponieważ tak naprawdę po prostu zastępuje jakiś tekst literalną wartością. Ma również tę zaletę, że nie ma typu, więc można go użyć dla dowolnej liczby całkowitej bez generowania ostrzeżeń.Zaletą „
const
” jest to, że można je przeskalować i można ich użyć w sytuacjach, gdy konieczne jest przekazanie wskaźnika do obiektu.Nie wiem dokładnie, do czego zmierzasz z częścią „
static
”. Jeśli deklarujesz globalnie, umieściłbym go w anonimowej przestrzeni nazw zamiast używaćstatic
. Na przykładźródło
#define
które mogą odnieść korzyść z bycia , przynajmniej jeśli mogą być użyte jako „bloki konstrukcyjne” dla większych stałych łańcuchowych. Zobacz moją odpowiedź na przykład.#define
Zaletą nie stosując żadnej pamięci jest niedokładna. „60” w tym przykładzie musi być gdzieś zapisane, bez względu na to,static const
czy jest lub#define
. W rzeczywistości widziałem kompilatory, w których użycie #define powodowało ogromne zużycie pamięci (tylko do odczytu), a statyczny const nie używał niepotrzebnej pamięci.Plusy i minusy między
#define
s,const
s (co zapomniałeś)enum
s, w zależności od użycia:enum
s:enum class X
są niejednoznaczne przez zakresX::
int
ale programista może go wyraźnie ustawićtemplate <typename T> void f(T t) { cout << ++t; }
nie kompiluje się, chociaż można zawijać wyliczenie do klasy za pomocą niejawnego konstruktora, operatora rzutowania i operatorów zdefiniowanych przez użytkownika)template <typename T> void f(T)
uzyskaj odrębną instancję, gdy przekażesz tę samą wartość liczbową z różnych wyliczeń, z których wszystkie różnią się od rzeczywistejf(int)
instancji. Kod obiektowy każdej funkcji może być identyczny (ignorując przesunięcia adresu), ale nie spodziewałbym się, że kompilator / linker wyeliminuje niepotrzebne kopie, chociaż możesz sprawdzić kompilator / linker, jeśli ci zależy.enum { A = 1, B = 2 }
- jestA|B
„legalne” z logiki programu perspektywiczny?)make
a inne narzędzia do ponownej kompilacji oparte na znacznikach czasu uruchomią rekompilację klienta po ich zmianie (źle! )const
s:#define
ala#define S std::string("abc")
, ale stała unika powtarzanej konstrukcji odrębnych elementów tymczasowych w każdym punkcie użytkowaniaconst
wartości, która minimalizuje pracę i wpływ przy przełączaniu między nimi#define
s:#define X "x"
, a niektóre Wykorzystanie klient ala"pre" X "post"
, jeśli chcesz lub musisz zrobić X runtime-zmienny zmienny niż stały wymusić zmiany w kodzie klienta (zamiast tylko rekompilacji), podczas gdy przejście jest łatwiejsze z punktu Aconst char*
czyconst std::string
dany oni już zmusza użytkownika do włączenia operacji konkatenacji (np."pre" + X + "post"
dlastring
)sizeof
bezpośrednio na zdefiniowanym literale liczbowymunsigned
){ 1, 2 }
które mogą być używane do inicjowania tablic#define MICROSECONDS *1E-6
itp. ( zdecydowanie nie polecam tego!)__FILE__
i__LINE__
mogą być włączone do podstawienia makr#if
instrukcjach pod kątem warunkowego włączenia kodu (mocniejszego niż postprocesing „jeśli”, ponieważ kod nie musi być kompilowany, jeśli nie zostanie wybrany przez preprocesor), użyj#undef
-ine, redefine itp.make
inne narzędzia do ponownej kompilacji oparte na znacznikach czasu uruchomią rekompilację klienta, gdy zostaną zmienione (źle!)Moja osobista opinia:
Zasadniczo używam
const
s i uważam je za najbardziej profesjonalną opcję do ogólnego użytku (chociaż inne mają prostotę podobającą się do tego starego leniwego programisty).źródło
Jeśli jest to pytanie C ++ i wspomniane
#define
jako alternatywa, to dotyczy stałych „globalnych” (tj. Zakresu pliku), a nie członków klasy. Jeśli chodzi o takie stałe w C ++static const
jest zbędne. W C ++const
domyślnie mają wewnętrzny link i nie ma sensu ich deklarowaćstatic
. Więc tak naprawdę chodzi oconst
vs.#define
.I wreszcie, w C ++
const
jest preferowane. Przynajmniej dlatego, że takie stałe są wpisywane i określane zasięgiem. Są po prostu nie ma powodów, aby wolą#define
ponadconst
, poza kilkoma wyjątkami.Stałe łańcuchowe BTW są jednym z przykładów takiego wyjątku. Za pomocą
#define
stałych łańcucha d można użyć funkcji kompilacji w czasie kompilacji kompilatorów C / C ++, jak wPS Znowu, na wszelki wypadek, gdy ktoś wymienia
static const
jako alternatywę#define
, zwykle oznacza to, że mówi o C, a nie o C ++. Zastanawiam się, czy to pytanie jest poprawnie oznaczone ...źródło
#define
może prowadzić do nieoczekiwanych wyników:Wysyła niepoprawny wynik:
Jeśli jednak zastąpisz to stałymi:
Wyprowadza poprawny wynik:
Jest tak, ponieważ
#define
po prostu zastępuje tekst. Ponieważ może to poważnie zepsuć kolejność operacji, zaleciłbym zamiast tego użycie zmiennej stałej.źródło
y
miał wartość5500
, trochę endian konkatenacjix
i 5.Używanie stałej statycznej jest jak używanie innych zmiennych const w kodzie. Oznacza to, że możesz śledzić, skąd pochodzą informacje, w przeciwieństwie do #define, które zostanie po prostu zastąpione w kodzie w procesie wstępnej kompilacji.
Możesz zajrzeć do C ++ FAQ Lite na to pytanie: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7
źródło
Zwykle powinieneś preferować stałe statyczne. Nie ma wady. Prprocesor powinien być używany głównie do kompilacji warunkowej (a czasem do naprawdę brudnych trików).
źródło
Definiowanie stałych przy użyciu dyrektywy preprocesora
#define
nie jest zalecane do zastosowania nie tylko wC++
, ale także wC
. Te stałe nie będą miały typu. Nawet wC
zaproponowano użycieconst
dla stałych.źródło
Zobacz tutaj: statyczna stała vs zdefiniuj
zwykle jest to deklaracja const (zauważ, że nie musi być statyczna)
źródło
Zawsze wolę używać funkcji języka niż niektórych dodatkowych narzędzi, takich jak preprocesor.
Z podstawowych wytycznych C ++
źródło
Jeśli definiujesz stałą, która ma być współużytkowana przez wszystkie instancje klasy, użyj stałej statycznej. Jeśli stała jest specyficzna dla każdej instancji, po prostu użyj const (ale zauważ, że wszystkie konstruktory klasy muszą zainicjować tę zmienną członka const na liście inicjalizacyjnej).
źródło