¿Las plantillas C ++ son solo macros disfrazadas?

He estado progtwigndo en C ++ durante algunos años, y he usado bastante STL y he creado mis propias clases de plantilla varias veces para ver cómo se hace.

Ahora trato de integrar las plantillas más profundamente en mi diseño de OO, y un pensamiento molesto sigue volviendo a mí: son solo macros, de verdad … Podrías implementar (bastante feos) auto_ptrs usando #defines, si realmente quería.

Esta forma de pensar acerca de las plantillas me ayuda a entender cómo funcionará realmente mi código, pero siento que de alguna manera me falta el punto. Las macros significan encarnación del mal, pero la “metaprogtwigción de plantillas” es furor.

Entonces, ¿CUÁLES SON las distinciones reales? y cómo pueden las plantillas evitar los peligros a los que #define te lleva, como

  • Errores de comstackdor inescrutables en lugares donde no los espera.
  • Código de hinchazón?
  • ¿Dificultad para rastrear el código?
  • Establecer puntos de interrupción del depurador?

Las macros son un mecanismo de sustitución de texto.

Las plantillas son un lenguaje funcional de turing completo que se ejecuta en tiempo de comstackción y está integrado en el sistema de tipo C ++. Puedes pensar en ellos como un mecanismo de complemento para el lenguaje.

Son analizados por el comstackdor y no por un preprocesador que se ejecuta antes del comstackdor.

Esto es lo que dice MSDN al respecto: http://msdn.microsoft.com/en-us/library/aa903548(VS.71).aspx

Aquí hay algunos problemas con la macro:

  • No hay forma de que el comstackdor verifique que los parámetros macro sean de tipos compatibles.
  • La macro se expande sin ninguna verificación de tipo especial.
  • Los parámetros i y j se evalúan dos veces. Por ejemplo, si cualquiera de los parámetros tiene una variable postincrementada, el incremento se realiza dos veces.
  • Debido a que el preprocesador amplía las macros, los mensajes de error del comstackdor se referirán a la macro expandida, en lugar de a la definición de la macro en sí misma. Además, la macro aparecerá en forma expandida durante la depuración.

Si eso no es suficiente para ti, no sé lo que es.

Aquí hay muchos comentarios que intentan diferenciar macros y plantillas.

Sí, ambos son lo mismo: herramientas de generación de código.

Las macros son una forma primitiva, sin mucha aplicación del comstackdor (como hacer Objetos en C: se puede hacer, pero no es bonita). Las plantillas son más avanzadas y tienen mucha mejor comprobación de tipos de comstackdor, mensajes de error, etc.

Sin embargo, cada uno tiene fortalezas que el otro no tiene.

Las plantillas solo pueden generar tipos de clase dynamics: las macros pueden generar casi cualquier código que desee (aparte de otra definición de macro). Las macros pueden ser muy útiles para insertar tablas estáticas de datos estructurados en su código.

Las plantillas, por otro lado, pueden lograr algunas cosas realmente DIVERTIDAS que no son posibles con las macros. Por ejemplo:

template class Unit { double value; public: Unit(double n) { value = n; } Unit operator+(Unit n) { return Unit(value + n.value); } Unit operator-(Unit n) { return Unit(value - n.value); } Unit operator*(double n) { return Unit(value * n); } Unit operator/(double n) { return Unit(value / n); } Unit operator*(Unit n) { return Unit(value + n.value); } Unit operator/(Unit n) { return Unit(value + n.value); } etc.... }; #define Distance Unit<1,0> #define Time Unit<0,1> #define Second Time(1.0) #define Meter Distance(1.0) void foo() { Distance moved1 = 5 * Meter; Distance moved2 = 10 * Meter; Time time1 = 10 * Second; Time time2 = 20 * Second; if ((moved1 / time1) == (moved2 / time2)) printf("Same speed!"); } 

La plantilla permite al comstackdor crear dinámicamente y usar instancias de tipo seguro de la plantilla sobre la marcha. El comstackdor realmente hace el matemático de parámetro de plantilla en tiempo de comstackción, creando clases separadas donde es necesario para cada resultado único. Existe un tipo implícito de Unidad <1, -1> (distancia / tiempo = velocidad) que se crea y se compara dentro del condicional, pero nunca se declara explícitamente en el código.

Aparentemente, alguien en una universidad ha definido una plantilla de este tipo con más de 40 parámetros (necesita una referencia), cada uno representa un tipo de unidad de física diferente. Piensa en la seguridad tipo de ese tipo de clase, solo para tus números.

La respuesta es tan larga que no puedo resumir todo excepto:

  • por ejemplo, las macros no garantizan la seguridad de tipo mientras que las plantillas de función: el comstackdor no puede verificar que los parámetros de macro son de tipos compatibles; también en el momento en que se crea una instancia de la plantilla, el comstackdor sabe si int o float definen operator +
  • las plantillas abren la puerta a la metaprogtwigción (en resumen, evaluar cosas y tomar decisiones en tiempo de comstackción): en tiempo de comstackción es posible saber si un tipo es integral o punto flotante; ya sea un puntero o si está const calificado, etc … ver “rasgos de tipo” en la próxima c ++ 0x
  • las plantillas de clase tienen especialización parcial
  • las plantillas de funciones tienen una especialización completa explícita, en su ejemplo, add(5, 3); podría implementarse de manera diferente que add(5, 3); que no es posible con macros
  • macro no tiene ningún scope
  • #define min(i, j) (((i) < (j)) ? (i) : (j)) - los parámetros i y j se evalúan dos veces. Por ejemplo, si cualquiera de los parámetros tiene una variable postincrementada, el incremento se realiza dos veces
  • debido a que el preprocesador amplía las macros, los mensajes de error del comstackdor se referirán a la macro expandida, en lugar de a la definición de la macro en sí misma. Además, la macro aparecerá en forma expandida durante la depuración
  • etc ...

Nota: en algunos casos excepcionales, prefiero confiar en las macros variadas porque no existen plantillas variadas hasta que c ++ 0x se convierte en mainstream. C ++ 11 es en vivo.

Referencias

  • C ++ FAQ Lite: [35] Plantillas
  • Ventajas de las plantillas
  • Plantillas vs. Macros (C ++)

En un nivel muy básico, sí, las plantillas son simplemente reemplazos de macro. Pero te estás saltando un montón de cosas al pensar de esa manera.

Considere la especialización de plantillas, que a mi conocimiento no puede simular con macros. No solo eso permite, bueno, la implementación especial para ciertos tipos, es una de las partes clave en la meta-progtwigción de plantillas:

 template  struct is_void { static const bool value = false; } template <> struct is_void { static const bool value = true; } 

Que en sí mismo es solo un ejemplo de las muchas cosas que puedes hacer . Las plantillas en sí mismas son Turing-completas.

Esto ignora las cosas más básicas, como el scope, la seguridad de tipo, y que las macros son más complicadas.

NO . Un simple ejemplo de contador: las plantillas siguen los espacios de nombres, los espacios de nombres de ignorar de la macro (ya que son declaraciones de preprocesador).

 namespace foo { template  NumberType add(NumberType a, NumberType b) { return a+b; } #define ADD(x, y) ((x)+(y)) } // namespace foo namespace logspace { // no problemo template  NumberType add(NumberType a, NumberType b) { return log(a)+log(b); } // redefintion: warning/error/bugs! #define ADD(x, y) (log(x)+log(y)) } // namespace logspace 

Las plantillas C ++ son como macros Lisp (no macros C) porque funcionan en la versión ya analizada del código y le permiten generar código arbitrario en tiempo de comstackción. Desafortunadamente, estás progtwigndo algo parecido al cálculo sin procesar de Lambda, por lo que las técnicas avanzadas como el bucle son bastante engorrosas. Para todos los detalles sangrientos, vea Progtwigción Generativa por Krysztof Czarnecki y Ulrich Eisenecker.

En caso de que esté buscando un tratamiento más profundo del tema, puedo convertirlo en el enemigo favorito de C ++ de todos . Este hombre sabe y odia más C ++ de lo que yo pueda soñar. Esto simultáneamente hace que el FQA sea increíblemente inflamatorio y un excelente recurso.

  • las plantillas son tipo seguras
  • los objetos / tipos con plantilla pueden ser espacios de nombres, miembros privados de una clase, etc.
  • los parámetros de las funciones con plantilla no se replican en todo el cuerpo de la función.

Estos realmente son un gran problema y evitar una multitud de errores.

Algo que no se ha mencionado es que las funciones de las plantillas pueden deducir tipos de parámetros.

 plantilla 
 void func (T t)
 {
   T make_another = t;

Se puede argumentar que el próximo operador de “tipo de letra” puede arreglarlo, pero incluso no puede romper otras plantillas:

 plantilla 
 void func (contenedor  c)

o incluso:

 Plantilla  class Contenedor, typename T>
 void func (Contenedor  ct)

También siento que el tema de la especialización no se cubrió lo suficiente. Aquí hay un ejemplo simple de lo que las macros no pueden hacer:

 plantilla 
 T min (T a, TB)
 {
   devuelve a 
 char * min (char * a, char * b)
 {
   if (strcmp (a, b) <0)
     devolver a;
   más
     return b;
 }

El espacio es demasiado pequeño para entrar en la especialización tipo, pero lo que puedes hacer con él, en lo que a mí respecta, es alucinante.

No, no es posible. El preprocesador es (apenas) suficiente para algunas cosas como contenedores de T, pero es simplemente insuficiente para muchas otras cosas que pueden hacer las plantillas.

Para algunos ejemplos reales, lea la Progtwigción moderna en C ++ , de Andre Alexandrescu, o la metaprogtwigción en C ++, de Dave Abrahams y Aleksey Gurtovoy. Casi nada de lo que se hace en ninguno de los libros puede simularse a un grado más que mínimo con el preprocesador.

Editar: en lo que respecta a typename , el requisito es bastante simple. El comstackdor no siempre puede determinar si un nombre dependiente se refiere a un tipo o no. El uso de typename explícitamente le dice al comstackdor que se refiere a un tipo.

 struct X { int x; }; struct Y { typedef long x; }; template  class Z { T::x; }; Z; // T::x == the int variable named x Z; // T::x == a typedef for the type 'long' 

typename le dice al comstackdor que un nombre particular está destinado a referirse a un tipo, no a una variable / valor, por lo que (por ejemplo) puede definir otras variables de ese tipo.

Esta respuesta pretende arrojar luz sobre el preprocesador C y cómo se puede utilizar para la progtwigción genérica


Son en algunos aspectos, ya que permiten una semántica similar. El preprocesador C se ha utilizado para habilitar algoritmos y estructuras de datos generics (Ver tokens Concatination ). Sin embargo, sin considerar ninguna otra característica de las plantillas de C ++, hace que todo el juego de progtwigción genérica sea MUCHO MÁS CLARO para leer e implementar.

Si alguien quiere ver el hardcore C, solo la progtwigción genérica en acción, lea el código fuente libevent ; esto también se menciona aquí . Se implementa una gran colección de contenedores / algoritmos, y se hace en un archivo de encabezado SINGLE (muy legible). Realmente admiro esto, el código de plantilla de C ++ (que prefiero para sus otros atributos) es MUY detallado.

Las plantillas son tipo seguras Con define, puede tener código que comstack, pero aún no funciona correctamente.

Las macros se expanden antes de que el comstackdor llegue al código. Esto significa que obtendrá un mensaje de error para el código expandido, y el depurador solo verá la versión expandida.

Con las macros, siempre existe la posibilidad de que alguna expresión se evalúe dos veces. Imagine pasar algo como ++ x como parámetro.

Las plantillas pueden colocarse en espacios de nombres o ser miembros de una clase. Las macros son solo un paso previo al procesamiento. Básicamente, las plantillas son un miembro de primera clase del lenguaje que se comporta agradable (¿más agradable?) Con todo lo demás.

Las plantillas pueden hacer mucho más de lo que el macroprocesador puede hacer.

Por ejemplo, hay especializaciones de plantilla: si esta plantilla se instancia con este tipo o constante, entonces no use la implementación predeterminada, pero esta aquí …

… las plantillas pueden exigir que algunos parámetros sean del mismo tipo, etc.


Aquí hay algunas fonts que tal vez quiera consultar:

  • Plantillas C ++ de Vandervoorde y Jossutis. Este es el mejor y más completo libro sobre plantillas que conozco.
  • La biblioteca de impulso consiste casi por completo en definiciones de plantilla.

Aunque los parámetros de la plantilla están verificados por tipos y hay muchas ventajas de las plantillas sobre las macros, las plantillas se parecen mucho a las macros, ya que aún se basan en la sustitución de texto. El comstackdor no verificará que el código de su plantilla tenga sentido hasta que le dé los parámetros de tipo para sustituir. Por ejemplo, Visual C ++ no se queja de esta función, siempre y cuando no la llame:

 template void Garbage(int a, int b) { fdsa uiofew & (a9 s) fdsahj += *! wtf; } 

En consecuencia, en general, es imposible saber si su código de plantilla funcionará correctamente o si se comstackrá correctamente para una categoría determinada de los parámetros de tipo que la plantilla está diseñada para aceptar.

En mi opinión, las macros son un mal hábito de C. Aunque pueden ser útiles para algunos, no veo una necesidad real de ellos cuando hay typedefs y plantillas. Las plantillas son la continuación natural de la Progtwigción Orientada a Objetos. Puedes hacer mucho más con plantillas …

Considera esto…

 int main() { SimpleList lstA; //... SimpleList lstB = lstA; //would normally give an error after trying to compile } 

Para hacer la conversión puede usar algo que se llama un constructor de conversión y un constructor de secuencia (ver el final) a lo largo del ejemplo bastante completo para una lista:

 #include  template class SimpleList { public: typedef T value_type; typedef std::size_t size_type; private: struct Knot { value_type val_; Knot * next_; Knot(const value_type &val) :val_(val), next_(0) {} }; Knot * head_; size_type nelems_; public: //Default constructor SimpleList() throw() :head_(0), nelems_(0) {} bool empty() const throw() { return size() == 0; } size_type size() const throw() { return nelems_; } private: Knot * last() throw() //could be done better { if(empty()) return 0; Knot *p = head_; while (p->next_) p = p->next_; return p; } public: void push_back(const value_type & val) { Knot *p = last(); if(!p) head_ = new Knot(val); else p->next_ = new Knot(val); ++nelems_; } void clear() throw() { while(head_) { Knot *p = head_->next_; delete head_; head_ = p; } nelems_ = 0; } //Destructor: ~SimpleList() throw() { clear(); } //Iterators: class iterator { Knot * cur_; public: iterator(Knot *p) throw() :cur_(p) {} bool operator==(const iterator & iter)const throw() { return cur_ == iter.cur_; } bool operator!=(const iterator & iter)const throw() { return !(*this == iter); } iterator & operator++() { cur_ = cur_->next_; return *this; } iterator operator++(int) { iterator temp(*this); operator++(); return temp; } value_type & operator*()throw() { return cur_->val_; } value_type operator*() const { return cur_->val_; } value_type operator->() { return cur_->val_; } const value_type operator->() const { return cur_->val_; } }; iterator begin() throw() { return iterator(head_); } iterator begin() const throw() { return iterator(head_); } iterator end() throw() { return iterator(0); } iterator end() const throw() { return iterator(0); } //Copy constructor: SimpleList(const SimpleList & lst) :head_(0), nelems_(0) { for(iterator i = lst.begin(); i != lst.end(); ++i) push_back(*i); } void swap(SimpleList & lst) throw() { std::swap(head_, lst.head_); std::swap(nelems_, lst.nelems_); } SimpleList & operator=(const SimpleList & lst) { SimpleList(lst).swap(*this); return *this; } //Conversion constructor template SimpleList(const SimpleList &lst) :head_(0), nelems_(0) { for(typename SimpleList::iterator iter = lst.begin(); iter != lst.end(); ++iter) push_back(*iter); } template SimpleList & operator=(const SimpleList &lst) { SimpleList(lst).swap(*this); return *this; } //Sequence constructor: template SimpleList(Iter first, Iter last) :head_(0), nelems_(0) { for(;first!=last; ++first) push_back(*first); } }; 

¡Eche un vistazo a la información de cplusplus.com en las plantillas ! Puede usar plantillas para hacer lo que se llama rasgos que se utiliza tiene una especie de documentación para tipos y tal. Puede hacer mucho más con las plantillas, ¡entonces qué es posible con las macros!

La palabra clave typename se presenta para habilitar typdef nesteds sin contexto. Estos fueron necesarios para la técnica de rasgo que permite que los metadatos se agreguen a los tipos (especialmente los tipos incorporados, como un puntero), esto fue necesario para escribir el STL. La palabra clave typename es igual a la palabra clave class.

Probemos un ejemplo primitivo. Considerar

 #define min(a,b) ((a)<(b))?(a):(b) 

invocado como

 c = min(a++,++b); 

Por supuesto, la diferencia real es más profunda, pero eso debería ser suficiente para descartar similitudes con las macros.

Editar : Y no, no puede garantizar la seguridad del tipo con macros. ¿Cómo implementaría typesafe min() para cada tipo que define menos que la comparación (es decir, operrator< )?

Las plantillas entienden los tipos de datos. Las macros no.

Esto significa que puedes hacer cosas como las siguientes …

  • Defina una operación (por ejemplo, una para envolver números ) que pueda tomar cualquier tipo de datos, luego proporcione especializaciones que elijan el algoritmo apropiado según si el tipo de datos es integral o flotante
  • Determine aspectos de sus tipos de datos en tiempo de comstackción, permitiendo trucos como la deducción de la plantilla del tamaño de la matriz , que Microsoft usa para sus sobrecargas en C ++ de strcpy_s y su tipo

Además, dado que las plantillas son seguras para tipos, existen varias técnicas de encoding de plantillas que podrían concebirse con algún preprocesador hipotético avanzado, pero que serían kludgy y propensas a errores en el mejor de los casos (por ejemplo, parámetros de plantilla de plantilla , argumentos de plantilla predeterminados, plantillas de política discutido en Modern C ++ Design ).

Las plantillas son solo similares a las macros en su funcionalidad más básica. Después de todo, las plantillas se introdujeron en el lenguaje como una alternativa “civilizada” a las macros. Pero incluso cuando se trata de la funcionalidad más básica, la similitud es solo superficial.

Sin embargo, una vez que llegamos a las características más avanzadas de las plantillas, como la especialización (parcial o explícita), cualquier similitud aparente con macros desaparece por completo.

Esto no es una respuesta sino una consecuencia de las respuestas ya indicadas.

Trabajando con científicos, cirujanos, artistas gráficos y otros que necesitan progtwigr, pero no son y nunca serán desarrolladores profesionales de software a tiempo completo, veo que las macros son fácilmente entendidas por el progtwigdor ocasional, mientras que las plantillas parecen requerir una mayor nivel de pensamiento abstracto posible solo con progtwigción de experiencia más profunda y continua en C ++. Se necesitan muchas instancias de trabajo con código donde las plantillas son un concepto útil, para que el concepto tenga sentido suficiente para su uso. Si bien esto podría decirse de cualquier función de idioma, la cantidad de experiencia para las plantillas presenta una brecha más grande que la que el progtwigdor casual especializado puede obtener de su trabajo diario.

El astrónomo o ingeniero en electrónica probablemente asimile las macros, incluso puede entender por qué se deben evitar las macros, pero no asimilará las plantillas lo suficientemente bien para el uso diario. En ese contexto, las macros son en realidad mejores. Naturalmente, existen muchos bolsillos de excepciones; algunos físicos corren círculos alrededor de los ingenieros de software pro, pero esto no es típico.

Hay algunos problemas básicos con las macros.

Primero, no respetan el scope o el tipo. Si tengo #define max(a, b)... , cada vez que tengo el token max en mi progtwig, por cualquier razón, será reemplazado. Se reemplazará si es un nombre de variable o en el interior de ámbitos nesteds. Esto puede causar errores de comstackción difíciles de encontrar. Por el contrario, las plantillas funcionan dentro del sistema de tipo C ++. Una función de plantilla puede tener su nombre reutilizado dentro de un scope, y no intentará reescribir un nombre de variable.

En segundo lugar, las macros no se pueden modificar. La plantilla std::swap normalmente solo declarará una variable temporal y realizará las asignaciones obvias, porque esa es la forma obvia que normalmente funciona. Eso es a lo que se limitaría una macro. Eso sería extremadamente ineficiente para vectores grandes, por lo que los vectores tienen un swap especial que intercambia las referencias en lugar de todo el contenido. (Esto resulta ser muy importante en cosas que el progtwigdor promedio de C ++ no debería escribir, pero usa).

En tercer lugar, las macros no pueden hacer ninguna forma de inferencia de tipo. En primer lugar, no se puede escribir una macro de intercambio genérico, ya que tendría que declarar una variable de un tipo y no saber cuál podría ser el tipo. Las plantillas son sensibles al tipo.

Un gran ejemplo del poder de las plantillas es lo que originalmente se llamaba Biblioteca de plantillas estándar, que se encuentra en el estándar como contenedores y algoritmos e iteradores. Eche un vistazo a cómo funcionan y trate de pensar cómo reemplazarlo con macros. Alexander Stepanov revisó una gran variedad de idiomas para implementar sus ideas de STL, y concluyó que C ++ con plantillas era el único en el que funcionaría.

Las plantillas ofrecen cierto grado de seguridad tipo.

Las plantillas están integradas en el lenguaje y son seguras para el tipo.

Dime cómo lo harías con las macros. Esta es una metaprogtwigción de plantillas pesadas.

https://www.youtube.com/watch?v=0A9pYr8wevk

Creo que las macros, AFAIK, no pueden calcular tipos de la forma en que las especializaciones parciales de plantillas pueden hacerlo.