¿Cuándo son beneficiosas las macros de C ++? [cerrado]

177

El preprocesador C es temido y rechazado por la comunidad C ++. Las funciones en línea, los consts y las plantillas suelen ser una alternativa más segura y superior a a #define.

La siguiente macro:

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

de ninguna manera es superior al tipo seguro:

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

Pero las macros tienen su lugar, enumere los usos que encuentra para las macros que no puede hacer sin el preprocesador.

Ponga cada caso de uso en una respuesta separada para que se pueda votar y si sabe cómo lograr una de las respuestas sin que el preprosesador indique cómo en los comentarios de esa respuesta.

Motti
fuente
Una vez tomé una aplicación C ++ llena de macros que tardó 45 minutos en compilar, reemplacé las macros con funciones en línea y reduje la compilación a menos de 15 minutos.
endian
El hilo trata sobre contextos en los que las macros son beneficiosas, no contextos en los que son subóptimos.
underscore_d

Respuestas:

123

Como contenedores para funciones de depuración, para pasar automáticamente cosas como __FILE__, __LINE__, etc:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif
Frank Szczerba
fuente
14
En realidad, el fragmento original: << ARCHIVO ":" << está bien, ARCHIVO genera una constante de cadena, que el preprocesador concatenará con ":" en una sola cadena.
Frank Szczerba
12
Esto solo requiere el preprocesador porque __FILE__y __LINE__ también requiere el preprocesador. Usarlos en su código es como un vector de infección para el preprocesador.
TED
93

Los métodos siempre deben ser completos, código compilable; Las macros pueden ser fragmentos de código. Por lo tanto, puede definir una macro foreach:

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

Y úsalo así:

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

Desde C ++ 11, esto es reemplazado por el bucle for basado en rango .

jdmichal
fuente
66
+1 Si está utilizando una sintaxis de iterador ridículamente compleja, escribir una macro de estilo foreach puede hacer que su código sea mucho más fácil de leer y mantener. Lo he hecho, funciona.
postfuturista
9
La mayoría de los comentarios son irrelevantes hasta el punto de que las macros pueden ser fragmentos de código en lugar de código completo. Pero gracias por la picardía.
jdmichal
12
Esto es C no C ++. Si está haciendo C ++, debería usar iteradores y std :: for_each.
chrish
20
No estoy de acuerdo, chrish. Antes de Lambda, for_eachera algo desagradable, porque el código por el que se ejecutaba cada elemento no era local al punto de llamada. foreach, (y recomiendo encarecidamente en BOOST_FOREACHlugar de una solución enrollada a mano), mantenga el código cerca del sitio de iteración, para que sea más legible. Dicho esto, una vez que se despliegue lambda, for_eachpodría ser una vez más el camino a seguir.
GManNickG
8
Y vale la pena señalar que BOOST_FOREACH es en sí mismo una macro (pero muy bien pensada)
Tyler McHenry
59

Los protectores de archivos de encabezado necesitan macros.

¿Hay otras áreas que requieren macros? No muchos (si los hay).

¿Hay otras situaciones que se benefician de las macros? ¡¡¡SI!!!

Un lugar donde uso macros es con código muy repetitivo. Por ejemplo, al envolver el código C ++ para usarlo con otras interfaces (.NET, COM, Python, etc.), necesito detectar diferentes tipos de excepciones. Así es como hago eso:

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

Tengo que poner estas capturas en cada función de contenedor. En lugar de escribir los bloques completos cada vez, simplemente escribo:

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

Esto también facilita el mantenimiento. Si alguna vez tengo que agregar un nuevo tipo de excepción, solo hay un lugar donde necesito agregarlo.

También hay otros ejemplos útiles: muchos de los cuales incluyen las macros __FILE__y __LINE__preprocesador.

De todos modos, las macros son muy útiles cuando se usan correctamente. Las macros no son malas, su mal uso es malo.

Kevin
fuente
77
La mayoría de los compiladores son compatibles en #pragma onceestos días, por lo que dudo que los guardias sean realmente necesarios
INFORMACIÓN 1800
13
Lo son si estás escribiendo para todos los compiladores en lugar de solo para la mayoría ;-)
Steve Jessop
30
Entonces, en lugar de la funcionalidad de preprocesador estándar portátil, ¿recomienda usar una extensión de preprocesador para evitar usar el preprocesador? Me parece algo ridículo.
Logan Capaldo
#pragma oncese rompe en muchos sistemas de construcción comunes.
Miles Rout
44
Hay una solución para eso que no requiere macros: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. Y en el lado funcional:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB
51

Principalmente:

  1. Incluir guardias
  2. Compilación condicional
  3. Informes (macros predefinidos como __LINE__y __FILE__)
  4. (rara vez) Duplicar patrones de código repetitivos.
  5. En el código de tu competidor.
David Thornley
fuente
Buscando ayuda sobre cómo obtener el número 5. ¿Me pueden guiar hacia una solución?
Max
50

Dentro de la compilación condicional, para superar problemas de diferencias entre compiladores:

#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
Andrew Stein
fuente
12
En C ++, lo mismo podría obtenerse mediante el uso de funciones en línea: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </code>
paercebal
2
Eso elimina los # define, pero no los #ifdef y #endif. De todos modos, estoy de acuerdo contigo.
Gorpik
19
NUNCA defina macros en minúsculas. Las macros para alterar las funciones son mi pesadilla (gracias, Microsoft). El mejor ejemplo es en primera línea. Muchas bibliotecas tienen closefunciones o métodos. Luego, cuando incluye el encabezado de esta biblioteca y el encabezado con esta macro, entonces tiene un gran problema, no puede usar la API de la biblioteca.
Marek R
AndrewStein, ¿ve algún beneficio en el uso de macros en este contexto sobre la sugerencia de @paercebal? Si no, parece que las macros son realmente gratuitas.
einpoklum
1
#ifdef WE_ARE_ON_WIN32por favor :)
ligereza corre en órbita el
38

Cuando desea hacer una cadena de una expresión, el mejor ejemplo para esto es assert( #xconvierte el valor de xen una cadena).

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");
Motti
fuente
55
Solo un poco, pero personalmente dejaría el punto y coma apagado.
Michael Myers
10
Estoy de acuerdo, de hecho, lo pondría en un do {} while (falso) (para evitar que se produzca un "highjacking") pero quería mantenerlo simple.
Motti
33

Las constantes de cadena a veces se definen mejor como macros, ya que puede hacer más con literales de cadena que con a const char *.

por ejemplo, los literales de cadena se pueden concatenar fácilmente .

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

Si const char *se usara a, entonces se tendría que usar algún tipo de clase de cadena para realizar la concatenación en tiempo de ejecución:

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);
Motti
fuente
2
En C ++ 11, consideraría que esta es la parte más importante (aparte de incluir guardias). Las macros son realmente lo mejor que tenemos para el procesamiento de cadenas en tiempo de compilación. Esa es una característica que espero que tengamos en C ++ 11 ++
David Stone
1
Esta es la situación que me llevó a desear macros en C #.
Rawling
2
Desearía poder +42 esto. Un aspecto muy importante, aunque no muy recordado, de los literales de cadena.
Daniel Kamil Kozar
24

Cuando desea cambiar el flujo del programa ( return, breaky continue) el código en una función se comporta de manera diferente que el código que está en línea en la función.

#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.
Motti
fuente
Lanzar una excepción me parece una mejor alternativa.
einpoklum
Al escribir extensiones de Python C (++), las excepciones se propagan estableciendo una cadena de excepción y luego devolviendo -1o NULL. Entonces, una macro puede reducir en gran medida el código repetitivo allí.
black_puppydog
20

Los obvios incluyen guardias

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif
Kena
fuente
17

No puede realizar un cortocircuito de los argumentos de llamada de función utilizando una llamada de función regular. Por ejemplo:

#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
1800 INFORMACIÓN
fuente
3
Quizás un punto más general: las funciones evalúan sus argumentos exactamente una vez. Las macros pueden evaluar argumentos más o menos veces.
Steve Jessop
@ [Greg Rogers] todo lo que hace el preprocesador macro es sustituir el texto. Una vez que comprenda eso, no debería haber más misterio al respecto.
1800 INFORMACIÓN
Puede obtener el comportamiento equivalente mediante la plantilla y andf en lugar de forzar la evaluación a bool antes de llamar a la función. Sin embargo, no me habría dado cuenta de lo que dijiste que era verdad sin probarlo. Interesante.
Greg Rogers
¿Cómo exactamente podrías hacer eso con una plantilla?
1800 INFORMACIÓN
66
Ocultar las operaciones de cortocircuito detrás de una macro de estilo de función es una de las cosas que realmente no quiero ver en el código de producción.
MikeMB
17

Digamos que ignoraremos cosas obvias como los protectores de cabecera.

A veces, desea generar código que el precompilador debe copiar / pegar:

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

que le permite codificar esto:

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

Y puede generar mensajes como:

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

Tenga en cuenta que mezclar plantillas con macros puede conducir a resultados aún mejores (es decir, generar automáticamente los valores lado a lado con sus nombres de variables)

Otras veces, necesita el __FILE__ y / o la __LINE__ de algún código, para generar información de depuración, por ejemplo. El siguiente es un clásico para Visual C ++:

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

Como con el siguiente código:

#pragma message(WRNG "Hello World")

genera mensajes como:

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

Otras veces, debe generar código utilizando los operadores de concatenación # y ##, como generar captadores y establecedores para una propiedad (esto es para casos bastante limitados, a través de).

Otras veces, generará código que no se compilará si se usa a través de una función, como:

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

Que se puede usar como

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

(Aún así, solo vi este tipo de código utilizado correctamente una vez )

Por último, pero no menos importante, los famosos 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 de código / pegado desde la página de inicio de boost)

Que es (en mi humilde opinión) mucho mejor que std::for_each.

Por lo tanto, las macros siempre son útiles porque están fuera de las reglas normales del compilador. Pero encuentro que la mayoría de las veces que veo uno, son efectivamente restos de código C que nunca se traducen a C ++ adecuado.

paercebal
fuente
1
Use el CPP solo para lo que el compilador no puede hacer. Por ejemplo, RAISE_ERROR_STL debe usar el CPP solo para determinar el archivo, la línea y la firma de la función, y pasarlos a una función (posiblemente en línea) que haga el resto.
Rainer Blome
Actualice su respuesta para reflejar C ++ 11 y aborde el comentario de @ RainerBlome.
einpoklum
@RainerBlome: Estamos de acuerdo. La macro RAISE_ERROR_STL es anterior a C ++ 11, por lo que, en ese contexto, está totalmente justificada. Según tengo entendido (pero nunca he tenido la oportunidad de lidiar con esas características específicas) es que puedes usar plantillas variadas (¿o macros?) En Modern C ++ para resolver el problema de manera más elegante.
paercebal
@einpoklum: "Actualice su respuesta para reflejar C ++ 11 y aborde el comentario de RainerBlome" No. :-). . . Creo que, en el mejor de los casos, agregaré una sección para Modern C ++, con implementaciones alternativas que reducen o eliminan la necesidad de macros, pero el punto es: las macros son feas y malvadas, pero cuando necesitas hacer algo, el compilador no comprende , lo haces a través de macros.
paercebal
Incluso con C ++ 11, mucho de lo que hace su macro puede dejarse para que lo haga una función: de #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; }esa manera, la macro es mucho más corta.
Rainer Blome
16

Los marcos de prueba de unidad para C ++ como UnitTest ++ giran en torno a las macros de preprocesador. Algunas líneas de código de prueba de unidad se expanden en una jerarquía de clases que no sería divertido escribir manualmente. Sin algo como UnitTest ++ y su magia de preprocesador, no sé cómo escribiría eficientemente pruebas unitarias para C ++.

Joe
fuente
Las pruebas unitarias son perfectamente posibles de escribir sin un marco. Al final, solo depende realmente del tipo de salida que desee. Si no le importa, un valor de salida simple que indique éxito o fracaso debería estar perfectamente bien.
Más claro
15

Temer al preprocesador C es como temer a las bombillas incandescentes solo porque obtenemos bombillas fluorescentes. Sí, el primero puede ser {electricidad | tiempo de programador} ineficiente. Sí, puedes quemarte (literalmente) por ellos. Pero pueden hacer el trabajo si lo manejas adecuadamente.

Cuando programa sistemas embebidos, C suele ser la única opción aparte del ensamblador de formularios. Después de programar en el escritorio con C ++ y luego cambiar a objetivos más pequeños e integrados, aprenderá a dejar de preocuparse por las "inelegancias" de tantas funciones C desnudas (macros incluidas) y simplemente tratar de descubrir el uso mejor y seguro que puede obtener de esas caracteristicas.

Alexander Stepanov dice :

Cuando programamos en C ++ no deberíamos avergonzarnos de su herencia de C, sino que debemos aprovecharla al máximo. Los únicos problemas con C ++, e incluso los únicos problemas con C, surgen cuando ellos mismos no son consistentes con su propia lógica.

VictorH
fuente
Creo que esta es la actitud equivocada. El hecho de que pueda aprender a "manejarlo adecuadamente" no significa que valga la pena el tiempo y el esfuerzo de nadie.
Neil G
9

Utilizamos las macros __FILE__y __LINE__para fines de diagnóstico en el lanzamiento, captura y registro de excepciones con gran cantidad de información, junto con escáneres automáticos de archivos de registro en nuestra infraestructura de control de calidad.

Por ejemplo, una macro de lanzamiento OUR_OWN_THROWpodría usarse con parámetros de tipo de excepción y constructor para esa excepción, incluida una descripción textual. Me gusta esto:

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

Por supuesto, esta macro arrojará la InvalidOperationExceptionexcepción con la descripción como parámetro constructor, pero también escribirá un mensaje en un archivo de registro que consta del nombre del archivo y el número de línea donde ocurrió el lanzamiento y su descripción textual. La excepción lanzada obtendrá una identificación, que también se registra. Si la excepción alguna vez se detecta en otro lugar del código, se marcará como tal y el archivo de registro indicará que se ha manejado esa excepción específica y que, por lo tanto, no es probable que sea la causa de un bloqueo que pueda registrarse más adelante. Nuestra infraestructura automatizada de control de calidad puede detectar fácilmente las excepciones no controladas.

Johann Gerell
fuente
9

Código de repetición.

Echa un vistazo para impulsar la biblioteca de preprocesadores , es una especie de meta-meta-programación. En tema-> motivación puedes encontrar un buen ejemplo.

Ruggero Turra
fuente
Casi todos, si no todos, los casos: la repetición de código se puede evitar con llamadas a funciones.
einpoklum
@einpoklum: no estoy de acuerdo. Echa un vistazo al enlace
Ruggero Turra
9

Algunas cosas muy avanzadas y útiles aún se pueden construir usando un preprocesador (macros), lo cual nunca se podría hacer usando las "construcciones de lenguaje" de c ++, incluidas las plantillas.

Ejemplos:

Hacer algo tanto un identificador C como una cadena

Manera fácil de usar variables de tipos de enumeración como cadena en C

Impulsar la metaprogramación del preprocesador

Suma
fuente
El tercer enlace está roto para tu información
Robin Hartland
Echa un vistazo stdio.hy sal.harchiva vc12para entender mejor.
Elshan
7

Ocasionalmente uso macros para poder definir información en un solo lugar, pero la uso de diferentes maneras en diferentes partes del código. Es solo un poco malvado :)

Por ejemplo, en "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

Luego, para una enumeración pública, se puede definir simplemente usando el nombre:

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

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

Y en una función init privada, todos los campos se pueden usar para llenar una tabla con los datos:

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

#include "field_list.h"
Andrew Johnson
fuente
1
Nota: se puede implementar una técnica similar incluso sin una inclusión por separado. Ver: stackoverflow.com/questions/147267/… stackoverflow.com/questions/126277/…
Suma
6

Un uso común es detectar el entorno de compilación, para el desarrollo multiplataforma puede escribir un conjunto de código para Linux, digamos, y otro para Windows cuando ya no exista una biblioteca multiplataforma para sus propósitos.

Entonces, en un ejemplo aproximado, un mutex multiplataforma puede tener

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

Para las funciones, son útiles cuando desea ignorar explícitamente la seguridad de tipos. Tales como los muchos ejemplos arriba y abajo para hacer ASSERT. Por supuesto, como muchas características de C / C ++, puedes dispararte en el pie, pero el lenguaje te brinda las herramientas y te permite decidir qué hacer.

Doug T.
fuente
Como el interrogador preguntó: esto se puede hacer sin macros al incluir diferentes encabezados a través de diferentes rutas de inclusión por plataforma. Sin embargo, me inclino a aceptar que las macros suelen ser más convenientes.
Steve Jessop
Secundo que. Si comienza a usar macros para ese propósito, el código puede volverse mucho menos legible rápidamente
Nemanja Trifunovic
6

Algo como

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

Para que pueda, por ejemplo, tener

assert(n == true);

y obtenga el nombre del archivo fuente y el número de línea del problema impreso en su registro si n es falso.

Si usa una llamada de función normal como

void assert(bool val);

en lugar de la macro, todo lo que puede obtener es el número de línea de su función de aserción impresa en el registro, lo que sería menos útil.

Keshi
fuente
¿Por qué reinventarías la rueda cuando las implementaciones de la Biblioteca estándar ya se proporcionan a través de <cassert>la assert()macro, que volca la información de archivo / línea / función? (en todas las implementaciones que he visto, de todos modos)
underscore_d
4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

A diferencia de la solución de plantilla 'preferida' discutida en un hilo actual, puede usarla como una expresión constante:

char src[23];
int dest[ARRAY_SIZE(src)];
revs fizzer
fuente
2
Esto se puede hacer con plantillas de una manera más segura (que no se compilará si se pasa un puntero en lugar de una matriz) stackoverflow.com/questions/720077/calculating-size-of-an-array/…
Motti
1
Ahora que tenemos constexpr en C ++ 11, la versión segura (no macro) también se puede usar en una expresión constante. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone
3

Puede usar #defines para ayudar con los escenarios de depuración y prueba unitaria. Por ejemplo, cree variantes de registro especiales de las funciones de memoria y cree un memlog_preinclude.h especial:

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

Compila tu código usando:

gcc -Imemlog_preinclude.h ...

Un enlace en tu memlog.o a la imagen final. Ahora puede controlar malloc, etc., tal vez con fines de registro o para simular fallas de asignación para pruebas unitarias.

Andrew Johnson
fuente
3

Cuando toma una decisión en tiempo de compilación sobre el comportamiento específico del Compilador / SO / Hardware.

Le permite configurar su interfaz para las características específicas de 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
Loki Astari
fuente
3

Utilizo macros para definir fácilmente Excepciones:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

donde DEF_EXCEPTION es

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

Los compiladores pueden rechazar su solicitud en línea.

Las macros siempre tendrán su lugar.

Algo que encuentro útil es #define DEBUG para el seguimiento de depuración: puede dejarlo 1 mientras depura un problema (o incluso dejarlo encendido durante todo el ciclo de desarrollo) y luego apagarlo cuando sea el momento de enviar.

pesado
fuente
10
Si el compilador rechaza su solicitud de en línea, podría tener una muy buena razón. Un buen compilador será mejor alineado correctamente que usted, y uno malo le dará más problemas de rendimiento que este.
David Thornley
@DavidThornley O podría no ser un gran compilador de optimización como GCC o CLANG / LLVM. Algunos compiladores son simplemente basura.
Miles Rout
2

En mi último trabajo, estaba trabajando en un escáner de virus. Para facilitarme la depuración, tuve un montón de registros atascados por todas partes, pero en una aplicación de alta demanda como esa, el costo de una llamada de función es demasiado costoso. Entonces, se me ocurrió esta pequeña Macro, que todavía me permitía habilitar el registro de depuración en una versión de lanzamiento en el sitio de un cliente, sin el costo de una llamada de función verificaría el indicador de depuración y simplemente regresaría sin registrar nada, o si estaba habilitado , haría el registro ... La macro se definió de la siguiente manera:

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

Debido a VA_ARGS en las funciones de registro, este fue un buen caso para una macro como esta.

Antes de eso, usé una macro en una aplicación de alta seguridad que necesitaba decirle al usuario que no tenía el acceso correcto, y que les diría qué bandera necesitaban.

La Macro (s) definida como:

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

Luego, podríamos rociar las comprobaciones por toda la interfaz de usuario, y le diría qué roles tenían permitido realizar la acción que intentó hacer, si aún no tenía ese rol. La razón para dos de ellos era devolver un valor en algunos lugares, y regresar de una función nula en otros ...

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

De todos modos, así es como los he usado, y no estoy seguro de cómo esto podría haber sido ayudado con plantillas ... Aparte de eso, trato de evitarlos, a menos que sea REALMENTE necesario.

LarryF
fuente
2

Otra macros foreach. T: tipo, c: contenedor, i: iterador

#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 (concepto que muestra, no real):

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

Mejores implementaciones disponibles: Google "BOOST_FOREACH"

Buenos artículos disponibles: Amor condicional: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html

No en lista
fuente
2

Quizás el mayor uso de las macros se encuentra en el desarrollo independiente de la plataforma. Piense en casos de inconsistencia de tipo: con macros, simplemente puede usar diferentes archivos de encabezado, como: --WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--programa.h

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

#include TYPES_H

Muy legible que implementarlo de otras maneras, en mi opinión.

rkellerm
fuente
2

Parece que VA_ARGS solo se han mencionado indirectamente hasta ahora:

Al escribir código genérico C ++ 03, y necesita un número variable de parámetros (genéricos), puede usar una macro en lugar de una plantilla.

#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: en general, el nombre de verificación / lanzamiento también podría incorporarse a la get_op_from_namefunción hipotética . Esto es solo un ejemplo. Puede haber otro código genérico que rodea la llamada VA_ARGS.

Una vez que obtengamos plantillas variadas con C ++ 11, podemos resolver esto "correctamente" con una plantilla.

Martin Ba
fuente
1

Creo que este truco es un uso inteligente del preprocesador que no se puede emular con una función:

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

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

Entonces puedes usarlo así:

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

También puede definir una macro RELEASE_ONLY.

Mathieu Pagé
fuente
2
Este truco no funciona según el estándar. Intenta crear un marcador de comentario a través del preprocesador, pero los comentarios deben eliminarse antes de que se ejecute el preprocesador. Un compilador conforme causará un error de sintaxis aquí.
David Thornley
2
Lo siento, David, pero el compilador debe contener una segunda copia de eliminación de comentarios.
Joshua
mucho más fácil es hacer que el indicador de depuración sea una constante global y usar un código como este: if (debug) cout << "..."; - no hay necesidad de macros!
Stefan Monov
@Stefan: De hecho, es lo que hago ahora. Cualquier compilador decente no generará ningún código si la depuración es falsa en ese caso.
Mathieu Pagé
1

Puede #defineconstantes en la línea de comando del compilador usando la opción -Do /D. Esto a menudo es útil cuando se realiza una compilación cruzada del mismo software para múltiples plataformas porque puede hacer que sus archivos MAKE controlen qué constantes se definen para cada plataforma.

bk1e
fuente