Kiedy makra C ++ są korzystne? [Zamknięte]


177

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.


Kiedyś wziąłem aplikację C ++ pełną makr, której zbudowanie zajęło 45 minut, zastąpiłem makra funkcjami wbudowanymi i skompilowałem kompilację do mniej niż 15 minut.
endian


Wątek dotyczy kontekstów, w których makra są korzystne, a nie kontekstów, w których są nieoptymalne.
underscore_d

Odpowiedzi:


123

Jako opakowania dla funkcji debugowania, aby automatycznie przechodzą takie rzeczy __FILE__, __LINE__itp:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif

14
Właściwie oryginalny fragment kodu: << FILE ":" << jest w porządku, PLIK generuje stałą łańcuchową, która zostanie połączona znakiem „:” w jeden ciąg przez preprocesor.
Frank Szczerba,

12
Wymaga to tylko preprocesora, ponieważ __FILE__i __LINE__ również wymaga preprocesora. Używanie ich w kodzie jest jak wektor infekcji dla preprocesora.
TED

93

Metody muszą być zawsze kompletnym, kompilowalnym kodem; makra mogą być fragmentami kodu. W ten sposób możesz zdefiniować każde makro:

#define foreach(list, index) for(index = 0; index < list.size(); index++)

I użyj go tak:

foreach(cookies, i)
    printf("Cookie: %s", cookies[i]);

Od C ++ 11 jest to zastępowane przez pętlę for opartą na zakresie .


6
+1 Jeśli używasz jakiejś absurdalnie złożonej składni iteratora, napisanie makra w każdym stylu może znacznie ułatwić czytanie i utrzymywanie kodu. Zrobiłem to, to działa.
postfuturist

9
Większość komentarzy jest całkowicie nieistotna do tego stopnia, że ​​makra mogą być fragmentami kodu, a nie całym kodem. Ale dziękuję za szukanie dziury w dziobie.
jdmichal

12
To jest C, a nie C ++. Jeśli robisz C ++, powinieneś używać iteratorów i std :: for_each.
chrish

20
Nie zgadzam się, chrish. Przed for_eachlambdami 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 polecam BOOST_FOREACHzamiast 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_eachmoże po raz kolejny być dobrym rozwiązaniem.
GManNickG,

8
I warto zauważyć, że BOOST_FOREACH samo w sobie jest makrem (ale bardzo dobrze przemyślanym)
Tyler McHenry

59

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

#define HANDLE_EXCEPTIONS \
catch (::mylib::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e); \
} \
catch (::std::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \
} \
catch (...) { \
    throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \
}

Muszę umieścić te zaczepy w każdej funkcji opakowania. Zamiast za każdym razem wpisywać pełne bloki catch, po prostu piszę:

void Foo()
{
    try {
        ::mylib::Foo()
    }
    HANDLE_EXCEPTIONS
}

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.


7
Większość kompilatorów obsługuje w #pragma oncedzisiejszych czasach, więc wątpię, czy strażnicy są naprawdę potrzebni
1800 INFORMACJA

13
Są, jeśli piszesz dla wszystkich kompilatorów, a nie tylko dla większości ;-)
Steve Jessop

30
Więc zamiast przenośnej, standardowej funkcjonalności preprocesora, zalecamy używanie rozszerzenia preprocesora, aby uniknąć używania preprocesora? Wydaje mi się to śmieszne.
Logan Capaldo

#pragma oncepsuje się w wielu popularnych systemach kompilacji.
Miles Rout

4
Istnieje rozwiązanie na to, że nie wymaga makra: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. A po stronie funkcji:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB

51

Przeważnie:

  1. Uwzględnij strażników
  2. Kompilacja warunkowa
  3. Raportowanie (predefiniowane makra, takie jak __LINE__i __FILE__)
  4. (rzadko) Powielanie powtarzających się wzorców kodu.
  5. W kodzie konkurenta.

Szukam pomocy, jak zrealizować numer 5. Czy możesz wskazać mi rozwiązanie?
Maksymalnie

50

Wewnątrz kompilacji warunkowej, aby przezwyciężyć problemy z różnicami między kompilatorami:

#ifdef ARE_WE_ON_WIN32
#define close(parm1)            _close (parm1)
#define rmdir(parm1)            _rmdir (parm1)
#define mkdir(parm1, parm2)     _mkdir (parm1)
#define access(parm1, parm2)    _access(parm1, parm2)
#define create(parm1, parm2)    _creat (parm1, parm2)
#define unlink(parm1)           _unlink(parm1)
#endif

12
W C ++ to samo można uzyskać poprzez użycie funkcji inline: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </code>
paercebal

2
To usuwa # define's, ale nie #ifdef i #endif. W każdym razie zgadzam się z tobą.
Gorpik,

19
NIGDY NIGDY nie definiuj makr z małymi literami. Makra do zmiany funkcji to mój koszmar (dziękuję Microsoft). Najlepszy przykład znajduje się w pierwszej linii. Wiele bibliotek ma closefunkcje 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.
Marek R

AndrewStein, czy widzisz jakąś korzyść z używania makr w tym kontekście w porównaniu z sugestią @ paercebal? Jeśli nie, wygląda na to, że makra są w rzeczywistości nieuzasadnione.
einpoklum

1
#ifdef WE_ARE_ON_WIN32plz :)
Lightness Races in Orbit

38

Kiedy chcesz utworzyć ciąg z wyrażenia, najlepszym przykładem jest assert( #xzamienia wartość xna łańcuch).

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");

5
Tylko czubek, ale osobiście zostawiłbym średnik bez.
Michael Myers

10
Zgadzam się, w rzeczywistości wstawiłbym to do {} while (false) (aby zapobiec innemu highjackingowi), ale chciałem, aby to było proste.
Motti

33

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 .

#define BASE_HKEY "Software\\Microsoft\\Internet Explorer\\"
// Now we can concat with other literals
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "Settings", &settings);
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "TypedURLs", &URLs);

Jeśli const char * użyto a, do wykonania konkatenacji w czasie wykonywania musiałby zostać użyty jakiś rodzaj klasy łańcuchowej:

const char* BaseHkey = "Software\\Microsoft\\Internet Explorer\\";
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "Settings").c_str(), &settings);
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "TypedURLs").c_str(), &URLs);

2
W C ++ 11 uznałbym to za najważniejszą część (poza włączaniem strażników). Makra są naprawdę najlepszą rzeczą, jaką mamy do przetwarzania łańcuchów w czasie kompilacji. Mam nadzieję, że ta funkcja pojawi się w C ++ 11 ++
David Stone

1
To jest sytuacja, która doprowadziła mnie do pragnienia makr w C #.
Rawling,

2
Chciałabym móc +42 to. Bardzo ważny, choć niezbyt często zapamiętany aspekt literałów tekstowych.
Daniel Kamil Kozar

24

Gdy chcesz zmienić przepływ programu ( return, breaki continue) kodu w funkcji zachowuje się inaczej niż kod, który jest faktycznie inlined w funkcji.

#define ASSERT_RETURN(condition, ret_val) \
if (!(condition)) { \
    assert(false && #condition); \
    return ret_val; }

// should really be in a do { } while(false) but that's another discussion.

Rzucenie wyjątku wydaje mi się lepszą alternatywą.
einpoklum

Podczas pisania rozszerzeń języka Python C (++) wyjątki są propagowane przez ustawienie ciągu wyjątku, a następnie zwrócenie -1lub NULL. Tak więc makro może znacznie zredukować kod standardowy.
black_puppydog

20

Oczywiste są strażnicy

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

17

Nie można skracać argumentów wywołań funkcji przy użyciu zwykłego wywołania funkcji. Na przykład:

#define andm(a, b) (a) && (b)

bool andf(bool a, bool b) { return a && b; }

andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
andf(x, y) // y will always be evaluated

3
Może bardziej ogólny punkt: funkcje oceniają swoje argumenty dokładnie raz. Makra mogą oceniać argumenty więcej lub mniej razy.
Steve Jessop

@ [Greg Rogers] wszystko, co robi preprocesor makra, to zastępowanie tekstu. Kiedy to zrozumiesz, nie powinno już być z tym tajemnic.
1800 INFORMACJA

Możesz uzyskać równoważne zachowanie, stosując szablony andf zamiast wymuszania wartości bool przed wywołaniem funkcji. Nie zdawałbym sobie jednak sprawy, że to, co powiedziałeś, jest prawdą, bez wypróbowania tego na sobie. Ciekawy.
Greg Rogers

Jak dokładnie mogłeś to zrobić za pomocą szablonu?
1800 INFORMACJA

6
Ukrywanie operacji zwarciowych za makrem stylu funkcji jest jedną z rzeczy, których naprawdę nie chcę widzieć w kodzie produkcyjnym.
MikeMB

17

Powiedzmy, że zignorujemy oczywiste rzeczy, takie jak osłony nagłówka.

Czasami chcesz wygenerować kod, który musi zostać skopiowany / wklejony przez prekompilator:

#define RAISE_ERROR_STL(p_strMessage)                                          \
do                                                                             \
{                                                                              \
   try                                                                         \
   {                                                                           \
      std::tstringstream strBuffer ;                                           \
      strBuffer << p_strMessage ;                                              \
      strMessage = strBuffer.str() ;                                           \
      raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
   }                                                                           \
   catch(...){}                                                                \
   {                                                                           \
   }                                                                           \
}                                                                              \
while(false)

który umożliwia zakodowanie tego:

RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;

I może generować wiadomości takie jak:

Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"

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

#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "

Jak z następującym kodem:

#pragma message(WRNG "Hello World")

generuje komunikaty takie jak:

C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World

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:

#define MY_TRY      try{
#define MY_CATCH    } catch(...) {
#define MY_END_TRY  }

Które mogą być używane jako

MY_TRY
   doSomethingDangerous() ;
MY_CATCH
   tryToRecoverEvenWithoutMeaningfullInfo() ;
   damnThoseMacros() ;
MY_END_TRY

(nadal, widziałem tylko ten rodzaj kodu słusznie użyty raz )

Wreszcie słynny boost::foreach!!!

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

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


1
Używaj CPP tylko do tego, czego kompilator nie może zrobić. Na przykład RAISE_ERROR_STL powinien używać CPP tylko do określenia podpisu pliku, wiersza i funkcji i przekazać je do funkcji (prawdopodobnie wbudowanej), która wykona resztę.
Rainer Blome

Zaktualizuj swoją odpowiedź, aby odzwierciedlała C ++ 11 i adres @ RainerBlome.
einpoklum

@RainerBlome: Zgadzamy się. Makro RAISE_ERROR_STL jest starsze niż C ++ 11, więc w tym kontekście jest w pełni uzasadnione. Zrozumiałem (ale nigdy nie miałem okazji zajmować się tymi specyficznymi funkcjami), że możesz użyć szablonów wariadycznych (lub makr?) W nowoczesnym C ++, aby bardziej elegancko rozwiązać problem.
paercebal

@einpoklum: „Zaktualizuj swoją odpowiedź, aby odzwierciedlała C ++ 11 i odpowiedz na komentarz RainerBlome” Nie. :-). . . Wierzę, że w najlepszym przypadku dodam sekcję dla Modern C ++, z alternatywnymi implementacjami zmniejszającymi lub eliminującymi potrzebę makr, ale chodzi o to: makra są brzydkie i złe, ale kiedy trzeba coś zrobić, kompilator nie rozumie , robisz to za pomocą makr.
paercebal

Nawet w C ++ 11 wiele z tego, co robi twoje makro, można pozostawić funkcji do wykonania: w #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.
Rainer Blome

16

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


Unittesty można pisać bez frameworka. Ostatecznie zależy to tylko od tego, jakiego rodzaju wyjścia chcesz. Jeśli cię to nie obchodzi, prosta wartość wyjścia wskazująca na sukces lub porażkę powinna być w porządku.
Jaśniejsze

15

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 :

Kiedy programujemy w C ++, nie powinniśmy się wstydzić jego dziedzictwa w C, ale w pełni go wykorzystywać. Jedyne problemy z C ++, a nawet jedyne problemy z C, pojawiają się, gdy oni sami nie są zgodni z własną logiką.


Myślę, że to niewłaściwe podejście. To, że możesz nauczyć się „właściwie sobie z tym radzić”, nie oznacza, że ​​jest to warte czyjegoś czasu i wysiłku.
Neil G

9

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_THROWmoże być używane z typem wyjątku i parametrami konstruktora dla tego wyjątku, w tym z opisem tekstowym. Lubię to:

OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));

To makro oczywiście wyrzuci InvalidOperationExceptionwyją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.



9

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


Trzecie ogniwo jest zerwane fyi
Robin Hartland

Przyjrzyj się stdio.hi sal.hprześlij, vc12aby lepiej zrozumieć.
Elshan

7

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

/*
 * List of fields, names and values.
 */
FIELD(EXAMPLE1, "first example", 10)
FIELD(EXAMPLE2, "second example", 96)
FIELD(ANOTHER, "more stuff", 32)
...
#undef FIELD

Następnie dla publicznego wyliczenia można zdefiniować, aby używała tylko nazwy:

#define FIELD(name, desc, value) FIELD_ ## name,

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

W prywatnej funkcji init wszystkie pola mogą zostać użyte do wypełnienia tabeli danymi:

#define FIELD(name, desc, value) \
    table[FIELD_ ## name].desc = desc; \
    table[FIELD_ ## name].value = value;

#include "field_list.h"

1
Uwaga: podobną technikę można zaimplementować nawet bez osobnego dołączania. Zobacz: stackoverflow.com/questions/147267/… stackoverflow.com/questions/126277/…
Suma

6

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

void lock()
{
    #ifdef WIN32
    EnterCriticalSection(...)
    #endif
    #ifdef POSIX
    pthread_mutex_lock(...)
    #endif
}

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


Ponieważ pytający zapytał: można to zrobić bez makr, dołączając różne nagłówki za pośrednictwem różnych ścieżek dołączania na platformę. Jestem skłonny zgodzić się, że makra są często wygodniejsze.
Steve Jessop

Popieram to. Jeśli zaczniesz używać makr w tym celu, kod może szybko stać się znacznie mniej czytelny
Nemanja Trifunovic

6

Coś jak

void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);

Abyś mógł na przykład mieć

assert(n == true);

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

void assert(bool val);

Zamiast makra, wszystko, co możesz uzyskać, to numer linii funkcji assert wydrukowany w dzienniku, co byłoby mniej przydatne.


Dlaczego chcesz wyważać otwartych drzwi, gdy implementacje biblioteki standardowej już zapewnić poprzez <cassert>na assert()makro, które zrzuca plik / wiersz / info funkcji? (w każdym razie we wszystkich implementacjach, które widziałem)
underscore_d

4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

W przeciwieństwie do `` preferowanego '' rozwiązania szablonu omawianego w bieżącym wątku, możesz użyć go jako stałego wyrażenia:

char src[23];
int dest[ARRAY_SIZE(src)];

2
Można to zrobić za pomocą szablonów w bezpieczniejszy sposób (który nie skompiluje się, jeśli zostanie przekazany wskaźnik zamiast tablicy) stackoverflow.com/questions/720077/calculating-size-of-an-array/ ...
Motti

1
Teraz, gdy mamy constexpr w C ++ 11, bezpieczna (niebędąca makrami) wersja może być również używana w stałym wyrażeniu. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone,

3

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:

#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free

Skompiluj swój kod za pomocą:

gcc -Imemlog_preinclude.h ...

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.


3

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.

#if defined(MY_OS1) && defined(MY_HARDWARE1)
#define   MY_ACTION(a,b,c)      doSothing_OS1HW1(a,b,c);}
#elif define(MY_OS1) && defined(MY_HARDWARE2)
#define   MY_ACTION(a,b,c)      doSomthing_OS1HW2(a,b,c);}
#elif define(MY_SUPER_OS)
          /* On this hardware it is a null operation */
#define   MY_ACTION(a,b,c)
#else
#error  "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration"
#endif

3

Używam makr do łatwego definiowania wyjątków:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

gdzie DEF_EXCEPTION jest

#define DEF_EXCEPTION(A, B) class A : public exception\
  {\
  public:\
    virtual const char* what() const throw()\
    {\
      return B;\
    };\
  }\

2

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


10
Jeśli kompilator odrzuci twoją prośbę o wstawianie, może to mieć bardzo dobry powód. Dobry kompilator będzie lepszy we właściwym wstawianiu niż ty, a zły spowoduje więcej problemów z wydajnością.
David Thornley

@DavidThornley Lub może nie być świetnym optymalizującym kompilatorem, takim jak GCC lub CLANG / LLVM. Niektóre kompilatory to po prostu bzdury.
Przebieg mil

2

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:

#define dbgmsg(_FORMAT, ...)  if((debugmsg_flag  & 0x00000001) || (debugmsg_flag & 0x80000000))     { log_dbgmsg(_FORMAT, __VA_ARGS__);  }

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:

#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))

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

SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR);

LRESULT CAddPerson1::OnWizardNext() 
{
   if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) {
      SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1;
   } else if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) {
      SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1;
   }
...

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.


2

Kolejne makra foreach. T: typ, c: kontener, i: iterator

#define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i)
#define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)

Użycie (koncepcja pokazująca, nierzeczywista):

void MultiplyEveryElementInList(std::list<int>& ints, int mul)
{
    foreach(std::list<int>, ints, i)
        (*i) *= mul;
}

int GetSumOfList(const std::list<int>& ints)
{
    int ret = 0;
    foreach_const(std::list<int>, ints, i)
        ret += *i;
    return ret;
}

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


2

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

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--program.h

#ifdef WIN32
#define TYPES_H "WINTYPES.H"
#else 
#define TYPES_H "POSIX_TYPES.H"
#endif

#include TYPES_H

Moim zdaniem dużo czytelne niż implementowanie go w inny sposób.


2

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.

#define CALL_RETURN_WRAPPER(FnType, FName, ...)          \
  if( FnType theFunction = get_op_from_name(FName) ) {   \
    return theFunction(__VA_ARGS__);                     \
  } else {                                               \
    throw invalid_function_name(FName);                  \
  }                                                      \
/**/

Uwaga: Ogólnie rzecz biorąc, nazwa check / rzut może być również uwzględniona w hipotetyceget_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.


1

Myślę, że ta sztuczka polega na sprytnym wykorzystaniu preprocesora, którego nie można emulować funkcją:

#define COMMENT COMMENT_SLASH(/)
#define COMMENT_SLASH(s) /##s

#if defined _DEBUG
#define DEBUG_ONLY
#else
#define DEBUG_ONLY COMMENT
#endif

Następnie możesz go użyć w ten sposób:

cout <<"Hello, World!" <<endl;
DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;

Możesz także zdefiniować makro RELEASE_ONLY.


2
Ta sztuczka nie działa zgodnie ze standardem. Próbuje utworzyć znacznik komentarza za pośrednictwem preprocesora, ale komentarze mają zostać usunięte przed uruchomieniem preprocesora. Zgodny kompilator spowoduje tutaj błąd składniowy.
David Thornley

2
Przepraszam David, ale kompilator musi zawierać drugą kopię usuwania komentarzy.
Joshua

znacznie łatwiej jest uczynić flagę debugowania globalną wartością stałą bool i użyć następującego kodu: if (debug) cout << "..."; - bez makr!
Stefan Monov

@Stefan: Rzeczywiście, tym się teraz zajmuję. Żaden przyzwoity kompilator nie wygeneruje żadnego kodu, jeśli w tym przypadku debugowanie jest fałszywe.
Mathieu Pagé

1

Możesz #definestałe w wierszu poleceń kompilatora przy użyciu opcji -Dlub /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.

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.