¿Cuándo son beneficiosas las macros C ++?

El preprocesador C es justificadamente temido y rechazado por la comunidad C ++. Las funciones, constelaciones y plantillas alineadas suelen ser una alternativa más segura y superior a un #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.

Por favor, coloque cada caso de uso en una respuesta separada para que pueda votar y si sabe cómo lograr una de las respuestas sin que el preescritor indique cómo en los comentarios de esa respuesta.

Como envoltorios 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 

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

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

Y úsalo como sigue:

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

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

Los protectores de archivos de encabezado necesitan macros.

¿Hay otras áreas que necesiten macros? No muchos (si hay alguno).

¿Hay alguna otra situación que se beneficie de las macros? ¡¡¡SÍ!!!

Un lugar donde uso macros es con código muy repetitivo. Por ejemplo, al ajustar 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 envoltura. En lugar de escribir los bloques de captura completos cada vez, simplemente escribo:

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

Esto también hace que el mantenimiento sea más fácil. Si alguna vez tengo que agregar un nuevo tipo de excepción, solo necesito agregar un lugar.

También hay otros ejemplos útiles: muchos de los cuales incluyen las macros del preprocesador __FILE__ y __LINE__ .

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

Comstackción condicional dentro, para superar problemas de diferencias entre comstackdores:

 #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 

Principalmente:

  1. Incluye guardias
  2. Comstackción condicional
  3. Informes (macros predefinidos como __LINE__ y __FILE__ )
  4. (raramente) Duplicar patrones de código repetitivos.
  5. En el código de tu competidor.

Cuando quiere hacer una cadena de una expresión, el mejor ejemplo para esto es assert ( #x convierte el valor de x en una cadena).

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

Las constantes de cadena a veces se definen mejor como macros, ya que puede hacer más con los literales de cadena que con un 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 se usó un const char * se debería 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); 

Cuando desee cambiar el flujo de progtwig ( return , break y continue ), el código de una función se comporta de manera diferente que el código que está realmente insertado 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. 

Lo obvio incluyen guardias

 #ifndef MYHEADER_H #define MYHEADER_H ... #endif 

No puede realizar un cortocircuito de los argumentos de llamada de función usando una llamada a función normal. 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 

Digamos que ignoraremos cosas obvias como guardias de encabezado.

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

 #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 llevar a resultados aún mejores (es decir, generar automáticamente los valores junto con sus nombres de variables)

Otras veces, necesita el __FILE__ y / o el __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, necesita generar código usando los operadores de concatenación # y ##, como generar getters y setters para una propiedad (esto es para casos bastante limitados).

Otras veces, generará código que no se comstackrá 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 correctamente usado una vez )

Por último, pero no menos importante, el famoso boost::foreach !!!

 #include  #include  #include  int main() { std::string hello( "Hello, world!" ); BOOST_FOREACH( char ch, hello ) { std::cout << ch; } return 0; } 

(Nota: copiar código / pegar desde la página principal de impulso)

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 comstackdor. Pero creo que la mayoría del tiempo que veo uno, son efectivamente rests de código C nunca traducidos a C ++.

Los marcos de prueba unitarios para C ++ como UnitTest ++ prácticamente giran en torno a las macros de los preprocesadores. Algunas líneas de código de prueba unitaria 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ías eficientemente las pruebas unitarias para C ++.

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

Cuando progtwig sistemas integrados, C es la única opción aparte del ensamblador. Después de progtwigr en el escritorio con C ++ y luego cambiar a objectives incrustados más pequeños, aprende a dejar de preocuparse por las “inelegias” de tantas características C desnudas (incluidas las macros) y simplemente tratando de descubrir el uso mejor y más seguro que puede obtener de esas caracteristicas.

Alexander Stepanov dice :

Cuando progtwigmos en C ++ no deberíamos avergonzarnos de su herencia C, sino hacer un uso completo de ella. Los únicos problemas con C ++, e incluso los únicos problemas con C, surgen cuando ellos mismos no son consistentes con su propia lógica.

Usamos las macros __FILE__ y __LINE__ con fines de diagnóstico en el lanzamiento, captura y registro de excepciones ricas en información, junto con los escáneres automáticos de archivos de registro en nuestra infraestructura de control de calidad.

Por ejemplo, una macro OUR_OWN_THROW se puede usar con el tipo de excepción y los parámetros del constructor para esa excepción, incluida una descripción textual. Me gusta esto:

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

Esta macro arrojará, por supuesto, la excepción InvalidOperationException con la descripción como parámetro de constructor, pero también escribirá un mensaje en un archivo de registro que constará del nombre del archivo y el número de línea donde ocurrió el lanzamiento y su descripción textual. La excepción arrojada obtendrá una identificación, que también se registrará. Si la excepción alguna vez se captura en otro lugar en el código, se marcará como tal y el archivo de registro indicará que esa excepción específica se ha manejado y que, por lo tanto, no es probable que sea la causa de ningún locking que pueda registrarse más adelante. Las excepciones no controladas pueden ser recogidas fácilmente por nuestra infraestructura de QA automatizada.

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

Ejemplos:

Hacer algo tanto un identificador de C como una cadena

Manera fácil de usar variables de tipos enum como cadena en C

Metaprogtwigción del preprocesador Boost

De vez en cuando uso macros para poder definir información en un solo lugar, pero usarla de diferentes maneras en diferentes partes del código. Es 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 usar el nombre:

 #define FIELD(name, desc, value) FIELD_ ## name, typedef field_ { #include "field_list.h" FIELD_MAX } field_en; 

Y en una función de inicio 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" 

Repetición de código

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

Un uso común es para detectar el entorno de comstackción, para el desarrollo multiplataforma puede escribir un conjunto de código para Linux, por ejemplo, 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 del tipo. Como los muchos ejemplos anteriores y posteriores para hacer ASSERT. Por supuesto, al igual que muchas características de C / C ++, puede dispararse en el pie, pero el lenguaje le brinda las herramientas y le permite decidir qué hacer.

Algo como

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

Para que solo puedas, por ejemplo, tener

 assert(n == true); 

y obtenga el nombre del archivo de origen y el número de línea del problema impresos 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 afirmar impreso en el registro, lo que sería menos útil.

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

Puede usar #defines para ayudar con los escenarios de depuración y prueba de unidades. 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 

Comstack tu código usando:

 gcc -Imemlog_preinclude.h ... 

Un enlace en tu memlog.o a la imagen final. Ahora controla malloc, etc., quizás con fines de registro, o para simular fallas de asignación para pruebas unitarias.

Yo uso macros para definir excepciones fácilmente:

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

Los comstackdores pueden rechazar su solicitud de estar en línea.

Las macros siempre tendrán su lugar.

Algo que considero útil es #define DEPURACIÓN para el rastreo de depuración: puede dejarlo 1 mientras depura un problema (o incluso lo deja durante todo el ciclo de desarrollo) y luego, apáguelo cuando llegue el momento de enviarlo.

Cuando toma una decisión en tiempo de comstackción sobre el comportamiento específico del comstackdor / sistema operativo / hardware.

Le permite hacer su interfaz a 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 

En mi último trabajo, estaba trabajando en un escáner de virus. Para hacer que todo sea más fácil de depurar, tenía un montón de registros atrapados por todas partes, pero en una aplicación de gran demanda como esa, el gasto de una llamada a función es demasiado caro. Entonces, se me ocurrió este pequeño Macro, que aún me permitía habilitar el registro de depuración en una versión de lanzamiento en un sitio de clientes, sin el costo de una llamada de función verificaría el indicador de depuración y simplemente regresaría sin iniciar sesión, 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, utilicé una macro en una aplicación de alta seguridad que necesitaba decirle al usuario que no tenían el acceso correcto, y les decí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)) 

Entonces, podríamos simplemente rociar los controles en toda la interfaz de usuario, y le diría qué roles se les permitió realizar la acción que intentó hacer, si aún no tenía esa función. El motivo para dos de ellos fue 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 se podría haber ayudado con las plantillas … Aparte de eso, trato de evitarlos, a menos que REALMENTE sea necesario.

Sin embargo, otras 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& ints, int mul) { foreach(std::list, ints, i) (*i) *= mul; } int GetSumOfList(const std::list& ints) { int ret = 0; foreach_const(std::list, 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

Tal vez el mayor uso de macros se encuentre en el desarrollo independiente de la plataforma. Piense en los casos de incoherencia de tipo: con las macros, puede simplemente usar diferentes archivos de encabezado, como: –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 

Mucho más legible que implementarlo de otras maneras, en mi opinión.

Parece que VA_ARGS solo se han mencionado indirectamente hasta el momento:

Al escribir código genérico C ++ 03 y necesita una cantidad variable de parámetros (generics), 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 check / throw también podría incorporarse a la función hipotética get_op_from_name . Esto es solo un ejemplo. Puede haber otro código genérico que rodee la llamada VA_ARGS.

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

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!" < 

También puede definir una macro RELEASE_ONLY.

Puede #define constantes en la línea de comando del comstackdor usando la opción -D o /D This is often useful when cross-compiling the same software for multiple platforms because you can have your makefiles control what constants are defined for each platform.