static const vs #define


212

Czy lepiej jest używać static constvarsa niż #definepreprocesora? A może zależy to od kontekstu?

Jakie są zalety / wady każdej metody?


14
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 {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
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:

  1. 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! )

  1. 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ę

  1. #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).


1
Ś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

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

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 ...


1
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 + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Wysyła niepoprawny wynik:

y is 505
z is 510

Jeśli jednak zastąpisz to stałymi:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Wyprowadza poprawny wynik:

y is 505
z is 1010

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.


1
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.

Możesz zajrzeć do C ++ FAQ Lite na to pytanie: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • 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).


3

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.



2

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.

Z podstawowych wytycznych C ++


0

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).

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.