C preprocesor jest uzasadniony strach i odrzucani przez społeczność C ++. Funkcje wbudowane, stałe i szablony są zwykle bezpieczniejszą i lepszą alternatywą dla #define
.
Następujące makro:
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
w żaden sposób nie przewyższa bezpiecznego typu:
inline bool succeeded(int hr) { return hr >= 0; }
Ale makra mają swoje miejsce, wymień zastosowania, które znajdziesz dla makr, których nie możesz zrobić bez preprocesora.
Proszę umieścić każdy przypadek użycia w osobnej odpowiedzi, aby można go było przegłosować, a jeśli wiesz, jak uzyskać jedną z odpowiedzi bez preprosora, wskaż, jak w komentarzach do odpowiedzi.
c++
c-preprocessor
Motti
źródło
źródło
Odpowiedzi:
Jako opakowania dla funkcji debugowania, aby automatycznie przechodzą takie rzeczy
__FILE__
,__LINE__
itp:źródło
__FILE__
i__LINE__
również wymaga preprocesora. Używanie ich w kodzie jest jak wektor infekcji dla preprocesora.Metody muszą być zawsze kompletnym, kompilowalnym kodem; makra mogą być fragmentami kodu. W ten sposób możesz zdefiniować każde makro:
I użyj go tak:
Od C ++ 11 jest to zastępowane przez pętlę for opartą na zakresie .
źródło
for_each
lambdami było to nieprzyjemne, ponieważ kod, przez który był wykonywany każdy element, nie był lokalny dla punktu wywołującego.foreach
, (i bardzo polecamBOOST_FOREACH
zamiast ręcznie rozwijanego rozwiązania) pozwól, abyś trzymał kod blisko witryny iteracyjnej, dzięki czemu był bardziej czytelny. To powiedziawszy, kiedy pojawi się lambda,for_each
może po raz kolejny być dobrym rozwiązaniem.Ochrona plików nagłówkowych wymaga makr.
Czy są jakieś inne obszary, które wymagają makr? Niewiele (jeśli w ogóle).
Czy są jakieś inne sytuacje, w których makra są korzystne? TAK!!!
Jedno miejsce, w którym używam makr, to bardzo powtarzalny kod. Na przykład podczas pakowania kodu C ++, który ma być używany z innymi interfejsami (.NET, COM, Python itp.), Muszę wychwycić różne typy wyjątków. Oto jak to robię:
Muszę umieścić te zaczepy w każdej funkcji opakowania. Zamiast za każdym razem wpisywać pełne bloki catch, po prostu piszę:
Ułatwia to również konserwację. Jeśli kiedykolwiek będę musiał dodać nowy typ wyjątku, jest tylko jedno miejsce, w którym muszę go dodać.
Są też inne przydatne przykłady: wiele z nich zawiera makra
__FILE__
i__LINE__
preprocesor.W każdym razie makra są bardzo przydatne, gdy są prawidłowo używane. Makra nie są złe - ich niewłaściwe użycie jest złe.
źródło
#pragma once
dzisiejszych czasach, więc wątpię, czy strażnicy są naprawdę potrzebni#pragma once
psuje się w wielu popularnych systemach kompilacji.void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }
. A po stronie funkcji:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
Przeważnie:
__LINE__
i__FILE__
)źródło
Wewnątrz kompilacji warunkowej, aby przezwyciężyć problemy z różnicami między kompilatorami:
źródło
close
funkcje lub metody. Wtedy, gdy dołączysz nagłówek tej biblioteki i nagłówek do tego makra, masz duży problem, nie możesz użyć biblioteki API.#ifdef WE_ARE_ON_WIN32
plz :)Kiedy chcesz utworzyć ciąg z wyrażenia, najlepszym przykładem jest
assert
(#x
zamienia wartośćx
na łańcuch).źródło
Stałe łańcuchowe są czasami lepiej definiowane jako makra, ponieważ można zrobić więcej z literałami łańcuchowymi niż z
const char *
.np. literały ciągów mogą być łatwo konkatenowane .
Jeśli
const char *
użyto a, do wykonania konkatenacji w czasie wykonywania musiałby zostać użyty jakiś rodzaj klasy łańcuchowej:źródło
Gdy chcesz zmienić przepływ programu (
return
,break
icontinue
) kodu w funkcji zachowuje się inaczej niż kod, który jest faktycznie inlined w funkcji.źródło
-1
lubNULL
. Tak więc makro może znacznie zredukować kod standardowy.Oczywiste są strażnicy
źródło
Nie można skracać argumentów wywołań funkcji przy użyciu zwykłego wywołania funkcji. Na przykład:
źródło
Powiedzmy, że zignorujemy oczywiste rzeczy, takie jak osłony nagłówka.
Czasami chcesz wygenerować kod, który musi zostać skopiowany / wklejony przez prekompilator:
który umożliwia zakodowanie tego:
I może generować wiadomości takie jak:
Zwróć uwagę, że mieszanie szablonów z makrami może prowadzić do jeszcze lepszych wyników (tj. Automatyczne generowanie wartości obok ich nazw zmiennych)
Innym razem potrzebujesz __FILE__ i / lub __LINE__ jakiegoś kodu, na przykład do wygenerowania informacji debugowania. Oto klasyczny przykład języka Visual C ++:
Jak z następującym kodem:
generuje komunikaty takie jak:
Innym razem musisz wygenerować kod za pomocą operatorów konkatenacji # i ##, takich jak generowanie metod pobierających i ustawiających dla właściwości (dotyczy to dość ograniczonych przypadków).
Innym razem wygenerujesz kod, który nie zostanie skompilowany, jeśli zostanie użyty przez funkcję, na przykład:
Które mogą być używane jako
(nadal, widziałem tylko ten rodzaj kodu słusznie użyty raz )
Wreszcie słynny
boost::foreach
!!!(Uwaga: kod skopiowany / wklejony ze strony głównej boost)
Co jest (IMHO) o wiele lepsze niż
std::for_each
.Zatem makra są zawsze przydatne, ponieważ znajdują się poza normalnymi regułami kompilatora. Ale okazuje się, że przez większość czasu są one pozostałościami kodu C, który nigdy nie został przetłumaczony na poprawne C ++.
źródło
#include <sstream> #include <iostream> using namespace std; void trace(char const * file, int line, ostream & o) { cerr<<file<<":"<<line<<": "<< static_cast<ostringstream & >(o).str().c_str()<<endl; } struct Oss { ostringstream s; ostringstream & lval() { return s; } }; #define TRACE(ostreamstuff) trace(__FILE__, __LINE__, Oss().lval()<<ostreamstuff) int main() { TRACE("Hello " << 123); return 0; }
ten sposób makro jest znacznie krótsze.Struktury testów jednostkowych dla C ++, takie jak UnitTest ++, w zasadzie obracają się wokół makr preprocesora. Kilka wierszy kodu testów jednostkowych rozwija się w hierarchię klas, których ręczne wpisywanie wcale nie byłoby przyjemne. Bez czegoś takiego jak UnitTest ++ i jego magii preprocesora, nie wiem, jak efektywnie napisać testy jednostkowe dla C ++.
źródło
Strach przed preprocesorem C to jak strach przed żarówkami tylko dlatego, że otrzymujemy żarówki fluorescencyjne. Tak, ten pierwszy może być {elektrycznością | czas programisty} nieefektywny. Tak, możesz zostać przez nie spalony (dosłownie). Ale mogą wykonać zadanie, jeśli odpowiednio sobie z tym poradzisz.
Kiedy programujesz systemy wbudowane, C jest jedyną opcją poza assemblerem. Po programowaniu na pulpicie w C ++, a następnie przełączeniu się na mniejsze, osadzone cele, nauczysz się przestać martwić się „nieelegancjami” tak wielu nagich funkcji C (w tym makr) i po prostu próbować znaleźć najlepsze i bezpieczne użycie, jakie możesz uzyskać z tych cechy.
Alexander Stepanov mówi :
źródło
Używamy
__FILE__
i__LINE__
makr do celów diagnostycznych w rzutowaniu wyjątków bogatych w informacje, przechwytywaniu i rejestrowaniu, wraz z automatycznymi skanerami plików dziennika w naszej infrastrukturze QA.Na przykład makro rzucające
OUR_OWN_THROW
może być używane z typem wyjątku i parametrami konstruktora dla tego wyjątku, w tym z opisem tekstowym. Lubię to:To makro oczywiście wyrzuci
InvalidOperationException
wyjątek z opisem jako parametrem konstruktora, ale zapisze również komunikat do pliku dziennika składający się z nazwy pliku i numeru wiersza, w którym wystąpił rzut, oraz jego opisu tekstowego. Zgłoszony wyjątek otrzyma identyfikator, który również zostanie zarejestrowany. Jeśli wyjątek zostanie kiedykolwiek przechwycony w innym miejscu w kodzie, zostanie oznaczony jako taki, a plik dziennika wskaże, że ten wyjątek został obsłużony i dlatego jest mało prawdopodobne, aby był on przyczyną awarii, która mogłaby zostać zalogowana później. Nieobsłużone wyjątki można łatwo wykryć dzięki naszej zautomatyzowanej infrastrukturze kontroli jakości.źródło
Powtarzanie kodu.
Spójrz na zwiększenie biblioteki preprocesorów , to rodzaj meta-meta-programowania. W temacie-> motywacja możesz znaleźć dobry przykład.
źródło
Niektóre bardzo zaawansowane i przydatne rzeczy mogą być nadal budowane przy użyciu preprocesora (makr), czego nigdy nie byłbyś w stanie zrobić używając „konstrukcji językowych” c ++, w tym szablonów.
Przykłady:
Tworzenie czegoś zarówno jako identyfikatora C, jak i łańcucha
Łatwy sposób używania zmiennych typów wyliczeniowych jako łańcuchów w C
Zwiększ metaprogramowanie preprocesora
źródło
stdio.h
isal.h
prześlij,vc12
aby lepiej zrozumieć.Czasami używam makr, więc mogę zdefiniować informacje w jednym miejscu, ale używam ich na różne sposoby w różnych częściach kodu. To tylko trochę złe :)
Na przykład w „field_list.h”:
Następnie dla publicznego wyliczenia można zdefiniować, aby używała tylko nazwy:
W prywatnej funkcji init wszystkie pola mogą zostać użyte do wypełnienia tabeli danymi:
źródło
Jednym z typowych zastosowań jest wykrywanie środowiska kompilacyjnego, w przypadku programowania międzyplatformowego można napisać jeden zestaw kodu, powiedzmy, dla systemu Linux, a drugi dla systemu Windows, gdy nie ma już biblioteki wieloplatformowej.
Tak więc w przybliżonym przykładzie może mieć wieloplatformowy mutex
W przypadku funkcji są one przydatne, gdy chcesz jawnie zignorować bezpieczeństwo typów. Takich jak wiele przykładów powyżej i poniżej dotyczących wykonywania ASSERT. Oczywiście, podobnie jak wiele funkcji C / C ++, możesz strzelić sobie w stopę, ale język zapewnia narzędzia i pozwala zdecydować, co zrobić.
źródło
Coś jak
Abyś mógł na przykład mieć
i uzyskaj nazwę pliku źródłowego i numer linii problemu, wypisane do twojego dziennika, jeśli n jest fałszywe.
Jeśli używasz normalnego wywołania funkcji, takiego jak
Zamiast makra, wszystko, co możesz uzyskać, to numer linii funkcji assert wydrukowany w dzienniku, co byłoby mniej przydatne.
źródło
<cassert>
naassert()
makro, które zrzuca plik / wiersz / info funkcji? (w każdym razie we wszystkich implementacjach, które widziałem)W przeciwieństwie do `` preferowanego '' rozwiązania szablonu omawianego w bieżącym wątku, możesz użyć go jako stałego wyrażenia:
źródło
template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
Możesz użyć #defines, aby pomóc w scenariuszach debugowania i testów jednostkowych. Na przykład utwórz specjalne warianty rejestrowania funkcji pamięci i utwórz specjalny plik memlog_preinclude.h:
Skompiluj swój kod za pomocą:
Link w twoim memlog.o do końcowego obrazu. Możesz teraz kontrolować malloc itp., Być może w celu rejestrowania lub symulowania błędów alokacji dla testów jednostkowych.
źródło
Gdy w czasie kompilacji podejmujesz decyzję dotyczącą zachowania specyficznego dla kompilatora / systemu operacyjnego / sprzętu.
Pozwala na stworzenie interfejsu do funkcji specyficznych dla kompilatora / systemu operacyjnego / sprzętu.
źródło
Używam makr do łatwego definiowania wyjątków:
gdzie DEF_EXCEPTION jest
źródło
Kompilatorzy mogą odrzucić Twoją prośbę o wbudowanie.
Makra zawsze będą miały swoje miejsce.
Coś, co uważam za przydatne, to #define DEBUG do śledzenia debugowania - możesz zostawić go 1 podczas debugowania problemu (lub nawet zostawić go włączonego podczas całego cyklu rozwoju), a następnie wyłączyć, gdy nadejdzie czas na wysyłkę.
źródło
W mojej ostatniej pracy pracowałem nad skanerem antywirusowym. Aby ułatwić mi debugowanie, miałem mnóstwo rejestrowania, które utknęło w każdym miejscu, ale w takiej aplikacji o dużym popycie koszt wywołania funkcji jest po prostu zbyt drogi. Tak więc wymyśliłem to małe makro, które nadal pozwoliło mi włączyć logowanie debugowania w wersji wydania na stronie klienta, bez kosztu wywołania funkcji sprawdzałoby flagę debugowania i po prostu wracało bez rejestrowania czegokolwiek lub jeśli jest włączone , zrobi rejestrację ... Makro zostało zdefiniowane w następujący sposób:
Ze względu na VA_ARGS w funkcjach dziennika był to dobry przypadek dla takiego makra.
Wcześniej użyłem makra w aplikacji o wysokim poziomie bezpieczeństwa, która musiała powiedzieć użytkownikowi, że nie ma odpowiedniego dostępu, i powie im, jakiej flagi potrzebują.
Makro (a) zdefiniowane jako:
Następnie moglibyśmy po prostu posypać kontrolami cały interfejs użytkownika i powie ci, które role mogą wykonywać akcję, którą próbowałeś wykonać, jeśli jeszcze nie masz tej roli. Powodem dwóch z nich było zwrócenie wartości w niektórych miejscach i powrót z funkcji void w innych ...
Tak czy inaczej, właśnie tak ich używałem i nie jestem pewien, jak można było w tym pomóc za pomocą szablonów ... Poza tym staram się ich unikać, chyba że NAPRAWDĘ jest to konieczne.
źródło
Kolejne makra foreach. T: typ, c: kontener, i: iterator
Użycie (koncepcja pokazująca, nierzeczywista):
Dostępne lepsze implementacje: Google „BOOST_FOREACH”
Dostępne dobre artykuły: Conditional Love: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html
źródło
Być może największe użycie makr jest w rozwoju niezależnym od platformy. Pomyśl o przypadkach niespójności typów - w przypadku makr możesz po prostu użyć różnych plików nagłówkowych, takich jak: --WIN_TYPES.H
--POSIX_TYPES.h
--program.h
Moim zdaniem dużo czytelne niż implementowanie go w inny sposób.
źródło
Wygląda na to, że VA_ARGS były do tej pory wspominane pośrednio:
Kiedy piszesz ogólny kod C ++ 03 i potrzebujesz zmiennej liczby (ogólnych) parametrów, możesz użyć makra zamiast szablonu.
Uwaga: Ogólnie rzecz biorąc, nazwa check / rzut może być również uwzględniona w hipotetyce
get_op_from_name
funkcji . To tylko przykład. Może istnieć inny ogólny kod otaczający wywołanie VA_ARGS.Gdy otrzymamy szablony wariadyczne w C ++ 11, możemy rozwiązać ten problem „poprawnie” za pomocą szablonu.
źródło
Myślę, że ta sztuczka polega na sprytnym wykorzystaniu preprocesora, którego nie można emulować funkcją:
Następnie możesz go użyć w ten sposób:
Możesz także zdefiniować makro RELEASE_ONLY.
źródło
Możesz
#define
stałe w wierszu poleceń kompilatora przy użyciu opcji-D
lub/D
. Jest to często przydatne podczas kompilowania tego samego oprogramowania na wielu platformach, ponieważ pliki makefile mogą kontrolować, jakie stałe są zdefiniowane dla każdej platformy.źródło