Quando sono utili le macro C ++? [chiuso]


177

Il preprocessore C è giustamente temuto e evitato dalla comunità C ++. Le funzioni, i cons e i template integrati sono in genere un'alternativa più sicura e superiore a a #define.

La seguente macro:

#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)  

non è in alcun modo superiore al tipo sicuro:

inline bool succeeded(int hr) { return hr >= 0; }

Ma le macro hanno il loro posto, ti preghiamo di elencare gli usi che trovi per le macro che non puoi fare a meno del preprocessore.

Si prega di inserire ogni caso d'uso in una risposta separata in modo che possa essere votato e se si sa come ottenere una delle risposte senza il pre-predecessore, indicare come nei commenti di quella risposta.


Una volta ho preso un'applicazione C ++ piena di macro che ha richiesto 45 minuti per essere compilata, ho sostituito le macro con funzioni incorporate e ho ridotto la build a meno di 15 minuti.
Endian,


Il thread riguarda i contesti in cui le macro sono utili, non i contesti in cui non sono ottimali.
underscore_d

Risposte:


123

Come wrapper per funzioni di debug, per passare automaticamente le cose come __FILE__, __LINE__, ecc:

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

14
In realtà, lo snippet originale: << FILE ":" << va bene, FILE genera una costante di stringa, che verrà concatenata con ":" in una singola stringa dal pre-processore.
Frank Szczerba,

12
Ciò richiede solo il preprocessore perché __FILE__e __LINE__ anche il preprocessore. Usarli nel tuo codice è come un vettore di infezione per il preprocessore.
TED

93

I metodi devono essere sempre completi, codice compilabile; le macro possono essere frammenti di codice. Quindi puoi definire una macro foreach:

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

E usalo così:

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

Dal C ++ 11, questo è sostituito dal basato su loop per .


6
+1 Se stai usando una sintassi iteratrice ridicolmente complessa, scrivere una macro di stile foreach può rendere il tuo codice molto più facile da leggere e mantenere. L'ho fatto, funziona.
postfuturista il

9
La maggior parte dei commenti è completamente irrilevante al punto che le macro possono essere frammenti di codice anziché codice completo. Ma grazie per il nitpicking.
jdmichal,

12
Questo è C non C ++. Se stai facendo C ++, dovresti usare iteratori e std :: for_each.
chrish,

20
Non sono d'accordo, Chrish. Prima di lambda, for_eachera una cosa brutta, perché il codice attraverso il quale scorreva ogni elemento non era locale al punto di chiamata. foreach, (e consiglio vivamente BOOST_FOREACHinvece di una soluzione a rotazione manuale) ti consentiamo di mantenere il codice vicino al sito di iterazione, rendendolo più leggibile. Detto questo, una volta che Lambda è stato lanciato, for_eachpotrebbe essere ancora una volta la strada da percorrere.
GManNickG,

8
E vale la pena notare che BOOST_FOREACH è esso stesso una macro (ma molto ben pensata)
Tyler McHenry,

59

Le protezioni dei file di intestazione richiedono macro.

Ci sono altre aree che richiedono macro? Non molti (se presenti).

Ci sono altre situazioni che beneficiano delle macro? SÌ!!!

Un posto in cui uso le macro è con un codice molto ripetitivo. Ad esempio, quando si esegue il wrapping del codice C ++ da utilizzare con altre interfacce (.NET, COM, Python, ecc ...), è necessario rilevare diversi tipi di eccezioni. Ecco come lo faccio:

#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__); \
}

Devo mettere queste catture in ogni funzione wrapper. Invece di digitare ogni volta i blocchi di cattura completi, scrivo solo:

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

Ciò semplifica anche la manutenzione. Se dovessi mai aggiungere un nuovo tipo di eccezione, c'è solo un posto in cui devo aggiungerlo.

Ci sono anche altri esempi utili: molti dei quali includono le macro __FILE__e il __LINE__preprocessore.

Comunque, le macro sono molto utili se usate correttamente. Le macro non sono cattive - il loro uso improprio è malvagio.


7
La maggior parte dei compilatori supporta #pragma oncequesti giorni, quindi dubito che le guardie siano davvero necessarie
1800 INFORMAZIONI

13
Lo sono se stai scrivendo per tutti i compilatori anziché solo per la maggior parte ;-)
Steve Jessop,

30
Quindi, invece della funzionalità portatile, del preprocessore standard, consigliamo di utilizzare un'estensione del preprocessore per evitare di usare il preprocessore? Mi sembra ridicolo.
Logan Capaldo

#pragma oncesi rompe su molti sistemi di build comuni.
Miles Rout,

4
C'è una soluzione per quello che non richiede macro: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. E dal lato della funzione:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB il

51

Soprattutto:

  1. Includi le protezioni
  2. Compilazione condizionale
  3. Rapporti (macro predefinite come __LINE__e __FILE__)
  4. (raramente) Duplicazione di schemi di codice ripetitivi.
  5. Nel codice del tuo concorrente.

In cerca di aiuto su come realizzare il numero 5. Puoi guidarmi verso una soluzione?
Max

50

All'interno della compilazione condizionale, per superare i problemi di differenze tra compilatori:

#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
In C ++, lo stesso si può ottenere usando le funzioni inline: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </code>
paercebal,

2
Ciò rimuove il # define's, ma non il #ifdef e #endif. Ad ogni modo, sono d'accordo con te.
Gorpik,

19
MAI MAI definire macro minuscole. Le macro per modificare le funzioni sono il mio incubo (grazie Microsoft). Il miglior esempio è in prima linea. Molte librerie hanno closefunzioni o metodi. Quindi quando includi un'intestazione di questa libreria e un'intestazione con questa macro che hai un grosso problema, non puoi utilizzare l'API della libreria.
Marek R,

AndrewStein, vedi qualche vantaggio sull'uso delle macro in questo contesto rispetto al suggerimento di @ paercebal? In caso contrario, sembra che le macro siano effettivamente gratuite.
einpoklum,

1
#ifdef WE_ARE_ON_WIN32plz :)
Razze di leggerezza in orbita

38

Quando si desidera creare una stringa da un'espressione, l'esempio migliore è assert( #xtrasforma il valore xin una stringa).

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

5
Solo un pignolo, ma personalmente vorrei lasciare il punto e virgola spento.
Michael Myers

10
Sono d'accordo, in effetti lo metterei in un do {} while (false) (per evitare altro highjacking) ma volevo mantenerlo semplice.
Motti,

33

Le costanti di stringa sono talvolta meglio definite come macro poiché puoi fare di più con i letterali di stringa che con a const char *.

ad es. i letterali stringa possono essere facilmente concatenati .

#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);

Se const char *fosse usato un, allora una sorta di classe stringa dovrebbe essere usata per eseguire la concatenazione in fase di esecuzione:

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
In C ++ 11, considererei questa la parte più importante (oltre a includere guardie). Le macro sono davvero la cosa migliore che abbiamo per l'elaborazione della stringa in fase di compilazione. Questa è una caratteristica che spero di ottenere in C ++ 11 ++
David Stone,

1
Questa è la situazione che mi ha portato a desiderare macro in C #.
Rawling,

2
Vorrei poterlo fare +42. Un aspetto molto importante, anche se non spesso ricordato, dei letterali a corda.
Daniel Kamil Kozar,

24

Quando si desidera modificare il flusso del programma ( return, breake continue) il codice in una funzione si comporta in modo diverso rispetto al codice effettivamente incorporato nella funzione.

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

Lanciare un'eccezione mi sembra un'alternativa migliore.
einpoklum,

Quando si scrivono estensioni Python C (++), le eccezioni vengono propagate impostando una stringa di eccezione, quindi restituendo -1o NULL. Quindi una macro può ridurre notevolmente il codice del boilerplate lì.
black_puppydog

20

L'ovvio include le guardie

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

17

Non è possibile eseguire il cortocircuito degli argomenti della chiamata di funzione utilizzando una normale chiamata di funzione. Per esempio:

#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
Forse un punto più generale: le funzioni valutano i loro argomenti esattamente una volta. Le macro possono valutare gli argomenti più volte o meno volte.
Steve Jessop,

@ [Greg Rogers] tutto il preprocessore macro fa testo sostitutivo. Una volta che lo capisci, non ci dovrebbe essere più mistero al riguardo.
1800 INFORMAZIONI,

È possibile ottenere il comportamento equivalente templatizzando andf invece di forzare la valutazione a bool prima di chiamare la funzione. Non avrei realizzato quello che hai detto fosse vero senza provarlo per me stesso. Interessante.
Greg Rogers,

Come hai potuto esattamente farlo con un modello?
1800 INFORMAZIONI,

6
Nascondere le operazioni di corto circuito dietro una macro di stile di funzione è una delle cose che non voglio davvero vedere nel codice di produzione.
MikeMB,

17

Diciamo che ignoreremo cose ovvie come le protezioni per i colpi di testa.

A volte, si desidera generare il codice che deve essere copiato / incollato dal precompilatore:

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

che ti consente di codificare questo:

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

E può generare messaggi come:

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

Si noti che la combinazione di modelli con macro può portare a risultati ancora migliori (ovvero generare automaticamente i valori fianco a fianco con i loro nomi variabili)

Altre volte, per generare informazioni di debug, ad esempio, è necessario __FILE__ e / o __LINE__ di alcuni codici. Di seguito è un classico per Visual C ++:

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

Come con il seguente codice:

#pragma message(WRNG "Hello World")

genera messaggi come:

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

Altre volte, è necessario generare codice utilizzando gli operatori di concatenazione # e ##, come la generazione di getter e setter per una proprietà (ciò avviene in casi piuttosto limitati, attraverso).

Altre volte, genererai codice che non verrà compilato se utilizzato tramite una funzione, come:

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

Quale può essere usato come

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

(comunque, ho visto solo questo tipo di codice usato correttamente una volta )

Ultimo, ma non meno importante, il famoso 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;
}

(Nota: copia del codice / incollato dalla home page boost)

Quale è (IMHO) molto meglio di std::for_each.

Quindi, le macro sono sempre utili perché sono al di fuori delle normali regole del compilatore. Ma trovo che la maggior parte delle volte che ne vedo uno, sono effettivamente resti di codice C mai tradotti in C ++ corretto.


1
Utilizzare il CPP solo per ciò che il compilatore non può fare. Ad esempio, RAISE_ERROR_STL dovrebbe utilizzare il CPP solo per determinare la firma di file, linee e funzioni e passarle a una funzione (possibilmente in linea) che fa il resto.
Rainer Blome,

Aggiorna la tua risposta per riflettere C ++ 11 e indirizza il commento di @ RainerBlome.
einpoklum,

@RainerBlome: siamo d'accordo. La macro RAISE_ERROR_STL è pre-C ++ 11, quindi in quel contesto è pienamente giustificata. La mia comprensione (ma non ho mai avuto l'occasione di occuparmi di quelle caratteristiche specifiche) è che puoi usare modelli variadici (o macro?) In Modern C ++ per risolvere il problema in modo più elegante.
paercebal,

@einpoklum: "Aggiorna la tua risposta per riflettere C ++ 11 e rivolgiti al commento di RainerBlome" No. :-). . . Credo, nella migliore delle ipotesi, aggiungerò una sezione per Modern C ++, con implementazioni alternative che riducono o eliminano la necessità di macro, ma il punto è importante: le macro sono brutte e malvagie, ma quando devi fare qualcosa il compilatore non capisce , lo fai tramite le macro.
paercebal,

Anche con C ++ 11, molto di ciò che fa la tua macro può essere lasciato a una funzione: in #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; }questo modo, la macro è molto più breve.
Rainer Blome,

16

I framework di unit test per C ++ come UnitTest ++ ruotano praticamente attorno a macro di preprocessore. Alcune righe di codice unit test si espandono in una gerarchia di classi che non sarebbe affatto divertente digitare manualmente. Senza qualcosa come UnitTest ++ e la sua magia preprocessore, non so come scriveresti in modo efficiente unit test per C ++.


Gli unittest sono perfettamente possibili per scrivere senza un framework. Alla fine, dipende solo dal tipo di output desiderato. Se non ti interessa, un semplice valore di uscita che indica il successo o il fallimento dovrebbe andare perfettamente bene.
Più chiaro

15

Temere il preprocessore C è come temere le lampadine a incandescenza solo perché otteniamo lampadine fluorescenti. Sì, il primo può essere {elettricità | tempo del programmatore} inefficiente. Sì, puoi essere (letteralmente) bruciato da loro. Ma possono fare il lavoro se lo gestisci correttamente.

Quando si programmano i sistemi incorporati, C è l'unica opzione a parte l'assemblatore di moduli. Dopo aver programmato sul desktop con C ++ e poi passato a target integrati più piccoli, impari a smettere di preoccuparti delle "ineleganze" di così tante funzionalità C nude (macro incluse) e solo di cercare di capire l'uso migliore e sicuro che puoi ottenere da quelle Caratteristiche.

Alexander Stepanov dice :

Quando programmiamo in C ++ non dovremmo vergognarci del suo patrimonio in C, ma sfruttarlo appieno. Gli unici problemi con C ++ e anche gli unici problemi con C sorgono quando essi stessi non sono coerenti con la propria logica.


Penso che questo sia l'atteggiamento sbagliato. Solo perché puoi imparare a "gestirlo correttamente" non significa che valga la pena il tempo e lo sforzo di chiunque.
Neil G

9

Utilizziamo macro __FILE__e __LINE__a scopo diagnostico per generare, intercettare e registrare eccezioni ricche di informazioni, insieme a scanner automatici di file di registro nella nostra infrastruttura di controllo qualità.

Ad esempio, una macro di lancio OUR_OWN_THROWpotrebbe essere utilizzata con il tipo di eccezione e i parametri del costruttore per tale eccezione, inclusa una descrizione testuale. Come questo:

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

Questa macro genererà ovviamente l' InvalidOperationExceptioneccezione con la descrizione come parametro del costruttore, ma scriverà anche un messaggio in un file di registro costituito dal nome del file e dal numero di riga in cui si è verificato il lancio e dalla sua descrizione testuale. L'eccezione generata otterrà un ID, che viene anche registrato. Se l'eccezione viene rilevata da qualche altra parte nel codice, verrà contrassegnata come tale e il file di registro indicherà quindi che l'eccezione specifica è stata gestita e che pertanto non è probabile che sia la causa di un arresto anomalo che potrebbe essere registrato in un secondo momento. Le eccezioni non gestite possono essere facilmente rilevate dalla nostra infrastruttura di controllo qualità automatizzata.



9

Alcune cose molto avanzate e utili possono ancora essere costruite usando il preprocessore (macro), che non saresti mai in grado di fare usando i "costrutti del linguaggio" c ++ inclusi i template.

Esempi:

Fare qualcosa sia un identificatore C che una stringa

Modo semplice per usare variabili di tipo enum come stringa in C

Potenzia la metaprogrammazione del preprocessore


Il terzo collegamento è interrotto
Robin Hartland,

Dai un'occhiata stdio.he sal.harchivia vc12per capire meglio.
Elshan,

7

Di tanto in tanto uso le macro in modo da poter definire le informazioni in un unico posto, ma le uso in modi diversi in diverse parti del codice. È solo leggermente malvagio :)

Ad esempio, in "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

Quindi per un enum pubblico può essere definito semplicemente il nome:

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

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

E in una funzione init privata, tutti i campi possono essere usati per popolare una tabella con i dati:

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

#include "field_list.h"

1
Nota: una tecnica simile può essere implementata anche senza un'inclusione separata. Vedi: stackoverflow.com/questions/147267/… stackoverflow.com/questions/126277/…
Suma,

6

Un uso comune è per rilevare l'ambiente di compilazione, per lo sviluppo multipiattaforma puoi scrivere un set di codice per Linux, diciamo, e un altro per Windows quando non esiste già una libreria multipiattaforma per i tuoi scopi.

Quindi, in un esempio approssimativo, può avere un mutex multipiattaforma

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

Per le funzioni, sono utili quando si desidera ignorare esplicitamente la sicurezza dei tipi. Come i molti esempi sopra e sotto per fare ASSERT. Naturalmente, come molte funzionalità C / C ++, puoi spararti ai piedi, ma la lingua ti dà gli strumenti e ti consente di decidere cosa fare.


Dal momento che l'interrogatore ha chiesto: questo può essere fatto senza macro includendo diverse intestazioni tramite percorsi di inclusione diversi per piattaforma. Sono propenso a concordare sul fatto che le macro sono spesso più convenienti.
Steve Jessop,

Sono d'accordo con questo. Se inizi a utilizzare le macro a tale scopo, il codice può diventare rapidamente molto meno leggibile
Nemanja Trifunovic,

6

Qualcosa di simile a

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

In modo che tu possa avere solo per esempio

assert(n == true);

e ottenere il nome del file di origine e il numero di riga del problema stampato nel registro se n è falso.

Se si utilizza una normale chiamata di funzione come

void assert(bool val);

invece della macro, tutto ciò che puoi ottenere è il numero di riga della tua funzione di asserzione stampato nel registro, il che sarebbe meno utile.


Perché reinventare la ruota quando le implementazioni della libreria standard forniscono già tramite <cassert>la assert()macro, che scarica le informazioni su file / linea / funzione? (in tutte le implementazioni che ho visto, comunque)
underscore_d

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

A differenza della soluzione modello 'preferita' discussa in un thread corrente, puoi usarla come espressione costante:

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

2
Questo può essere fatto con modelli in un modo più sicuro (che non verrà compilato se viene passato un puntatore anziché un array) stackoverflow.com/questions/720077/calculating-size-of-an-array/…
Motti

1
Ora che abbiamo constexpr in C ++ 11, la versione sicura (non macro) può essere usata anche in un'espressione costante. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone,

3

È possibile utilizzare #define per semplificare il debug e gli scenari di unit test. Ad esempio, creare varianti di registrazione speciali delle funzioni di memoria e creare uno speciale memlog_preinclude.h:

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

Compila il tuo codice usando:

gcc -Imemlog_preinclude.h ...

Un link nel tuo memlog.o all'immagine finale. Ora controlli malloc, ecc., Forse per scopi di registrazione, o per simulare errori di allocazione per unit test.


3

Quando si prende una decisione al momento della compilazione sul comportamento specifico del compilatore / OS / Hardware.

Ti permette di rendere la tua interfaccia alle funzioni specifiche di Comppiler / OS / Hardware.

#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

Uso le macro per definire facilmente le eccezioni:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

dove DEF_EXCEPTION è

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

2

I compilatori possono rifiutare la tua richiesta di inline.

Le macro avranno sempre il loro posto.

Qualcosa che trovo utile è #define DEBUG per la traccia di debug: puoi lasciarlo 1 durante il debug di un problema (o addirittura lasciarlo attivo durante l'intero ciclo di sviluppo), quindi spegnerlo quando è il momento della spedizione.


10
Se il compilatore rifiuta la tua richiesta di inline, potrebbe avere un'ottima ragione. Un buon compilatore sarà in grado di integrarsi meglio di quello che sei, e uno cattivo ti darà più problemi di prestazioni di così.
David Thornley,

@DavidThornley O potrebbe non essere un compilatore ottimizzante come GCC o CLANG / LLVM. Alcuni compilatori sono solo una schifezza.
Miles Rout,

2

Nel mio ultimo lavoro, stavo lavorando su uno scanner antivirus. Per semplificare il debug delle cose, ho avuto molte registrazioni bloccate ovunque, ma in un'app molto richiesta come quella, le spese di una chiamata di funzione sono semplicemente troppo costose. Quindi, mi è venuta in mente questa piccola macro, che mi ha ancora permesso di abilitare la registrazione di debug su una versione di rilascio in un sito di clienti, senza il costo di una chiamata di funzione controllerebbe il flag di debug e ritornerebbe senza registrare nulla, o se abilitato , farebbe la registrazione ... La macro è stata definita come segue:

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

A causa del VA_ARGS nelle funzioni di registro, questo è stato un buon caso per una macro come questa.

Prima di allora, ho usato una macro in un'applicazione ad alta sicurezza che doveva dire all'utente che non avevano l'accesso corretto e che avrebbe detto loro di quale bandiera avevano bisogno.

Le macro definite come:

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

Quindi, potremmo semplicemente cospargere i controlli in tutta l'interfaccia utente e ti dirà quali ruoli sono stati autorizzati a svolgere l'azione che hai provato a fare, se non avessi già quel ruolo. La ragione per due di loro era di restituire un valore in alcuni punti e di ritornare da una funzione nulla in altri ...

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

Ad ogni modo, è così che li ho usati, e non sono sicuro di come questo avrebbe potuto essere aiutato con i template ... A parte questo, cerco di evitarli, a meno che REALMENTE necessario.


2

Ancora un altro foreach macro. T: tipo, c: contenitore, i: iteratore

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

Uso (concetto che mostra, non reale):

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;
}

Migliori implementazioni disponibili: Google "BOOST_FOREACH"

Buoni articoli disponibili: Amore condizionale: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html


2

Forse il grande uso delle macro è nello sviluppo indipendente dalla piattaforma. Pensa ai casi di incoerenza dei tipi - con le macro, puoi semplicemente utilizzare diversi file di intestazione - come: --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

Molto leggibile che implementarlo in altri modi, secondo me.


2

Sembra che VA_ARGS sia stato menzionato solo indirettamente finora:

Quando si scrive un codice C ++ 03 generico e è necessario un numero variabile di parametri (generici), è possibile utilizzare una macro anziché un modello.

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

Nota: in generale, il controllo / lancio del nome potrebbe anche essere incorporato nell'ipoteticoget_op_from_name funzione . Questo è solo un esempio. Potrebbe esserci un altro codice generico che circonda la chiamata VA_ARGS.

Una volta ottenuti i modelli variadic con C ++ 11, possiamo risolverlo "correttamente" con un modello.


1

Penso che questo trucco sia un uso intelligente del preprocessore che non può essere emulato con una funzione:

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

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

Quindi puoi usarlo in questo modo:

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

Puoi anche definire una macro RELEASE_ONLY.


2
Questo trucco non funziona secondo lo standard. Tenta di creare un marcatore di commento tramite il preprocessore, ma i commenti devono essere rimossi prima dell'esecuzione del preprocessore. Un compilatore conforme causerà qui un errore di sintassi.
David Thornley,

2
Scusa David, ma il compilatore deve contenere una seconda copia della rimozione dei commenti.
Giosuè,

molto più semplice è rendere il flag di debug un valore globale e usare un codice come questo: if (debug) cout << "..."; - nessuna necessità di macro!
Stefan Monov,

@Stefan: In effetti, è quello che faccio ora. Qualsiasi compilatore decente non genererà alcun codice se il debug è falso in quel caso.
Mathieu Pagé,

1

È possibile #definecostanti nella riga di comando del compilatore utilizzando l' opzione -Do /D. Ciò è spesso utile durante la compilazione incrociata dello stesso software per più piattaforme poiché è possibile fare in modo che i propri makefile controllino le costanti definite per ciascuna piattaforma.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.