2009-10-28 15 views
159

Czy lepiej używać vars niż static const niż #define preprocesora? A może to zależy od kontekstu?static const vs #define

Jakie są zalety/wady dla każdej metody?

+12

Scott Meyers uwzględnia tę kwestię bardzo ładnie i dokładnie. Jego przedmiot nr 2 w "Effective C++ Third Edition". Dwa specjalne przypadki (1) statyczna const jest preferowana w zakresie klasy dla stałych specyficznych dla klasy; (2) przestrzeń nazw lub anonimowy zakres jest preferowany w stosunku do #define. – Eric

+2

Wolę Enums. Ponieważ jest hybrydą obu. Nie zajmuje miejsca, chyba że utworzysz jego zmienną. Jeśli chcesz tylko użyć jako stałej, wyliczenie jest najlepszą opcją. Ma typ bezpieczeństwa w C/C++ 11 std, a także idealną stałą. #define jest typu niebezpiecznego, const zajmuje miejsce, jeśli kompilator nie może go zoptymalizować. – siddhusingh

+0

Moja decyzja dotycząca użycia '# define' lub' static const' (dla ciągów) jest sterowana przez ** aspekt inicjalizacji ** (nie było to wspomniane w poniższych odpowiedziach): jeśli stała jest używana tylko w określonej jednostce kompilacji, to Używam 'static const', w przeciwnym razie używam' # define' - unikam inicjalizacji kolejności statycznej ** fiasko ** https://isocpp.org/wiki/faq/ctors#static-init-order –

Odpowiedz

107

Osobiście nienawidzę preprocesora, więc zawsze chodziłem z const.

Główną zaletą #define jest to, że nie wymaga pamięci do przechowywania w programie, ponieważ tak naprawdę po prostu zastępuje jakiś tekst dosłowną wartością. Ma także tę zaletę, że nie ma typu, więc może być użyty dla dowolnej wartości całkowitej bez generowania ostrzeżeń.

Zalety "const" to to, że mogą one być zakresowe i mogą być używane w sytuacjach, w których wskaźnik do obiektu musi zostać przekazany.

Nie wiem dokładnie, co robisz ze "statyczną" częścią. Jeśli deklarujesz globalnie, umieściłbym to w anonimowym obszarze nazw zamiast używać statycznego. Na przykład

namespace { 
    unsigned const seconds_per_minute = 60; 
}; 

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

To właśnie chcę wiedzieć. W mojej firmie strategia polega na tym, aby cały ciąg stał się ciągiem #define w osobnym pliku. Myślałem, że może lepiej użyć zamiast tego vars const. Dzięki –

+7

* Ciąg * stałe są jednym z tych, które mogą skorzystać z '# define'd, przynajmniej jeśli mogą być użyte jako" klocki "dla większych stałych ciągów.Zobacz moją odpowiedź na przykład. – AnT

+0

Sprawa, której nie rozważałam. Dziękuję za Twój czas. –

5

Używanie statycznej stałej jest jak używanie innych zmiennych stałych w kodzie. Oznacza to, że możesz śledzić, skąd pochodzą informacje, w przeciwieństwie do # definicji, które zostaną po prostu zastąpione w kodzie w procesie wstępnej kompilacji.

Czasami warto spojrzeć na C++ FAQ Lite dla tego pytania: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7

36

Jeśli jest to C++ pytanie wymienia #define jako alternatywa, to jest o „globalne” (czyli plików Zakres), a nie o stałych członków klasy. Jeśli chodzi o takie stałe w C++ static const jest zbędny. W C++ const mają domyślnie wewnętrzne powiązania i nie ma sensu deklarować ich jako static. Tak naprawdę jest to około const vs. #define.

I na koniec w C++ const jest preferowany. Przynajmniej dlatego, że takie stałe są wpisane i mają zasięg. Po prostu nie ma powodów, aby preferować #define ponad const, poza kilkoma wyjątkami.

Stałe łańcuchowe, BTW, są jednym z przykładów takiego wyjątku. Z #define stałych d ciąg można użyć w czasie kompilacji funkcji połączeniem kompilatory 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 const jako alternatywę dla #define, zwykle oznacza to, że mówią o C, a nie o C++. Zastanawiam się, czy to pytanie jest oznaczone prawidłowo ...

4
  • Statyczny const jest wpisany (ma typ) i może być sprawdzana przez kompilator dla ważności, redefinicja itp
  • #define można Redifined niezdefiniowane cokolwiek.

Zwykle powinieneś preferować statyczne consts.Nie ma żadnych wad. Procesor powinien być używany głównie do kompilacji warunkowej (a czasem dla naprawdę brudnych sztuczek).

194

Plusy i minusy do wszystkiego, w zależności od sposobu użytkowania:

  • teksty stałe
    • możliwe tylko w przypadku całkowitej wartości
    • prawidłowo scoped problemy zderzenie/identyfikator obsługiwane ładnie, szczególnie w 11 klasach enum C++ gdzie wyliczenia dla enum class X są ujednoznaczniane za pomocą zakresu silnie wpisanego, ale do wielkości int o dużym sygnaturach lub niepodpisanych, na które nie masz wpływu w C++ 03 (chociaż możesz określić pole bitowe, w którym powinny być spakowane, jeśli enum jest członkiem struct/class/union), podczas gdy C++ 11 domyślnie jest int, ale może być jawnie ustawione przez programistę
    • nie może przyjąć adresu - nie ma takiego adresu, ponieważ wartości wyliczeniowe są skutecznie zastępowane w punktach użycia w celu zwiększenia bezpieczeństwa użytkowania (np. inkrementowanie - template <typename T> void f(T t) { cout << ++t; } nie będzie się kompilować, ale można zawrzeć wyliczenie w klasie z niejawnym konstruktorem, operatorem odlewania i operatorami definiowanymi przez użytkownika).
    • Typ każdej stałej z wyliczenia zamykającego, a więc template <typename T> void f(T) uzyskać wyraźną instancję po przekroczeniu ta sama wartość liczbowa z różnych tabel, z których każda różni się od rzeczywistej instancji f(int). Kod obiektu każdej funkcji może być identyczny (ignorując przesunięcia adresów), ale nie spodziewałbym się, że kompilator/linker wyeliminuje niepotrzebne kopie, chociaż mógłbyś sprawdzić kompilator/linker, jeśli ci na tym zależy.
    • nawet z typeof/decltype, nie można oczekiwać, że numeric_limits dostarczą użytecznego wglądu w zestaw znaczących wartości i kombinacji (faktycznie, "prawne" kombinacje nie są nawet notowane w kodzie źródłowym, rozważ enum { A = 1, B = 2 } - jest A|B "legalne ? "z punktu widzenia logiki programu)
    • typename wyliczenia mogą pojawiać się w różnych miejscach w RTTI, komunikaty kompilatora itp - być użyteczny, ewentualnie zaciemniania
    • nie można używać wyliczenie bez jednostka tłumaczenie faktycznie widząc wartość , co oznacza wyliczenia w bibliotekach API wymagają wartości wyeksponowanych w nagłówku, a make i inne narzędzia do ponownej kompilacji oparte na znacznikach czasowych spowodują ponowne rekompilowanie klienta, gdy zostaną zmienione (złe!)
  • consts
    • prawidłowo scoped kwestie clash/identyfikator obsługiwane ładnie
    • silny, pojedynczy, typ określony przez użytkownika
      • może spróbować "typ" A #define ala #define S std::string("abc"), ale stały unika powtarzalna konstrukcja odrębnych tymczasników w każdym punkcie użycia
    • Jedna definicja Powikłania reguły
    • może przyjmować adresy, tworzyć odwołania do nich itp.
    • najbardziej podobny do nieprzestrzegania wartości const, co minimalizuje wpływ pracy i jeśli przełączanie pomiędzy wartością dwa
    • może być umieszczony wewnątrz pliku wdrażania, pozwalając zlokalizowaną Przekompiluj i tylko linki klienta odebrać zmiana
  • definiuje
    • „globalne” zakres/bardziej podatne na sprzecznych zwyczajów, które mogą produkować problemów trudnych do zdecydowania kompilacji i nieoczekiwane rezultaty run-time zamiast rozsądnych komunikatów o błędach; łagodzenia tego wymaga:
      • długie, niejasne i/lub koordynowanych centralnie identyfikatory, a dostęp do nich nie mogą korzystać z niejawnie dopasowywania// Koenig spojrzał w górę, aliasy nazw bieżącej przestrzeni nazw itp
      • natomiast trumping najlepsza praktyka pozwala, aby identyfikatory parametrów szablonu były jednoliterowymi dużymi literami (ewentualnie po nich liczba), inne użycie identyfikatorów bez małych liter jest tradycyjnie zarezerwowane i oczekiwane od definicji preprocesora (poza nagłówkami bibliotek OS i C/C++). Jest to ważne, aby można było zarządzać procesem preprocesora na skalę przedsiębiorstwa. Biblioteki stron trzecich mogą być zgodne. Obserwacja tego implikuje, że migracja istniejących consts lub enums do/z definicji wymaga zmiany wielkości liter, a więc wymaga edycji kodu źródłowego klienta, a nie "prostego" rekompilacji. (Osobiście pierwszą literę z wyliczeń, ale nie consts, więc będę hit migracji pomiędzy tymi dwoma też - być może czas, aby przemyśleć to.) Możliwe
      • operacje
    • więcej czasu kompilacji: string literal konkatenacji, stringification (biorąc ich wielkości), konkatenacji do identyfikatorów
      • minusem jest to, że biorąc pod uwagę #define X "x" i pewnego użytkowania klienta ala "pre" X "post", jeśli chcesz lub musisz zrobić Xa zmiennej runtime-zmienny niż stały wymusić zmiany w kodzie klienta (zamiast po prostu ponownej kompilacji), podczas gdy przejście to jest łatwiejsze od const char* lub const std::string, biorąc pod uwagę, że już zmuszają użytkownika do włączenia konkat operacje enacyjne (np. "pre" + X + "post" do string)
    • nie można używać sizeof bezpośrednio określona numeryczny dosłownym
    • bez typu (GCC nie ostrzec porównaniu do unsigned)
    • niektóre sieci kompilator/łączące/debugera mogą nie jest obecna Identyfikator, więc zostaniesz zredukowany do patrzenia na "magiczne liczby" (łańcuchy, cokolwiek ...)
    • nie może przyjąć adresu
    • podstawiona wartość nie musi być zgodna z prawem (lub dyskretna) w kontekście, w którym #define jest tworzony, tak jak jest oceniany w każdym punkcie użycia, aby można było odwoływać się do jeszcze nie zadeklarowanych obiektów, zależą od "implementacji", która nie musi być wstępnie zawarta, utwórz "stałe", takie jak { 1, 2 }, które mogą być użyte do inicjalizacji tablic, lub #define MICROSECONDS *1E-6 itd. (zdecydowanie nie polecając to!)
    • pewne szczególne rzeczy, jak __FILE__ i __LINE__ mogą zostać włączone do makra
    • można przetestować dla istnienia i wartości w #if sprawozdania za warunkowo tym kod (mocniejszy niż post- wstępne przetwarzanie "jeśli", ponieważ kod nie musi być kompilowany, jeśli nie został wybrany przez preprocesor), należy użyć #undef -ty, ponownie zdefiniować itd.
    • podstawione tekst musi być wystawiony:
      • w jednostce tłumaczeniowej jest używany przez, co oznacza, makr w bibliotekach do użytku klienta musi być w nagłówku, więc make i inne narzędzia rekompilacji datownika oparte wywoła rekompilacji klienta, gdy są one zmieniane (zły!)
      • lub w wierszu polecenia, gdzie potrzebna jest jeszcze bardziej troska, aby upewnić się rekompilacji kodu klienta (np Makefile lub skryptu dostarczanie definicji powinien być wymieniony jako zależność)

Zasadniczo używam const s i uważam je za najbardziej profesjonalną opcję ogólnego zastosowania (chociaż inne mają prostotę, która przemawia do tego starego leniwego programisty).

+17

+1: Doceń podsumowanie – Chubsdad

+1

Niesamowita odpowiedź. Jeden mały nit: czasami używam lokalnych wyliczeń, które nie są w nagłówkach w ogóle dla jasności kodu, jak w małych maszynach państwowych i tym podobnych. Nie muszą więc przez cały czas znajdować się w nagłówkach. – kert

+0

@kert: stały punkt ... odpowiednio edytował. Dzięki. –

1

Definiowanie stałych za pomocą dyrektywy preprocesora #define nie jest zalecane do stosowania nie tylko w C++, ale także w C. Te stałe nie będą miały tego typu. Nawet w C zaproponowano użycie stałych stałych w postaci const.

0

Jeśli definiujesz stałą, która ma być współużytkowana wśród wszystkich wystąpień klasy, użyj statycznej stałej. Jeśli stała jest specyficzna dla każdej instancji, po prostu użyj const (pamiętaj jednak, że wszystkie konstruktory klasy muszą zainicjować tę zmienną składową const na liście inicjalizacji).

0

Zawsze preferuj korzystanie z funkcji językowych w stosunku do niektórych dodatkowych narzędzi, takich jak preprocesor.

ES.31: Nie używać makr dla stałych lub „funkcjach”

makra są głównym źródłem błędów. Makra nie są zgodne ze zwykłymi zakresami i regułami typu. Makra nie są zgodne ze zwykłymi regułami przekazywania danych. Makra zapewniają, że ludzki czytelnik widzi coś innego niż to, co widzi kompilator. Makra komplikują tworzenie narzędzi.

Od C++ Core Guidelines