tipos template punteros puntero programacion plantillas pila herencia generica funciones estatico con clase casteo c++ templates macros

template - puntero clase c++



¿Las plantillas C++ son solo macros disfrazadas? (25)

He estado programando 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 "metaprogramació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 compilador 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?

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

template <typename T> 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:

template <typename T> void func(container<T> c)

o incluso:

template <tempate <typename> class Container, typename T> void func(Container<T> 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:

template <typename T> T min(T a, T B) { return a < b ? a : b; } template <> char* min(char* a, char* b) { if (strcmp(a, b) < 0) return a; else 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.


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 compilador 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<class T> 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 compilará correctamente para una categoría determinada de los parámetros de tipo que la plantilla está diseñada para aceptar.


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.


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 Programación Orientada a Objetos. Puedes hacer mucho más con plantillas ...

Considera esto...

int main() { SimpleList<short> lstA; //... SimpleList<int> 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 <algorithm> template<class T> 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<class U> SimpleList(const SimpleList<U> &lst) :head_(0), nelems_(0) { for(typename SimpleList<U>::iterator iter = lst.begin(); iter != lst.end(); ++iter) push_back(*iter); } template<class U> SimpleList & operator=(const SimpleList<U> &lst) { SimpleList(lst).swap(*this); return *this; } //Sequence constructor: template<class Iter> 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!


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-programación de plantillas:

template <typename T> struct is_void { static const bool value = false; } template <> struct is_void<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 alcance, la seguridad de tipo, y que las macros son más complicadas.


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 programar, pero no son y nunca serán desarrolladores profesionales de software a tiempo completo, veo que las macros son fácilmente entendidas por el programador ocasional, mientras que las plantillas parecen requerir una mayor nivel de pensamiento abstracto posible solo con programació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 programador 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 alcance o el tipo. Si tengo #define max(a, b)... , cada vez que tengo el token max en mi programa, por cualquier razón, será reemplazado. Se reemplazará si es un nombre de variable o en el interior de ámbitos anidados. Esto puede causar errores de compilació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 alcance, 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 programador 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.


Hay muchos comentarios tratando de 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 compilador (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 compilador, mensajes de error, etc.

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

Las plantillas solo pueden generar tipos de clase dinámicos: 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<int d,int t> class Unit { double value; public: Unit(double n) { value = n; } Unit<d,t> operator+(Unit<d,t> n) { return Unit<d,t>(value + n.value); } Unit<d,t> operator-(Unit<d,t> n) { return Unit<d,t>(value - n.value); } Unit<d,t> operator*(double n) { return Unit<d,t>(value * n); } Unit<d,t> operator/(double n) { return Unit<d,t>(value / n); } Unit<d+d2,t+t2> operator*(Unit<d2,t2> n) { return Unit<d+d2,t+t2>(value + n.value); } Unit<d-d2,t-t2> operator/(Unit<d2,t2> n) { return Unit<d-d2,t-t2>(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 compilador crear dinámicamente y usar instancias de tipo seguro de la plantilla sobre la marcha. El compilador realmente hace el matemático de parámetro de plantilla en tiempo de compilació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 palabra clave typename se presenta para habilitar typdef anidados 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.


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 compilador 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 compilador sabe si int o float definen operator +
  • las plantillas abren la puerta a la metaprogramación (en resumen, evaluar cosas y tomar decisiones en tiempo de compilación): en tiempo de compilació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<float>(5, 3); podría implementarse de manera diferente que add<int>(5, 3); que no es posible con macros
  • macro no tiene ningún alcance
  • #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 compilador 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


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 compilación y está integrado en el sistema de tipo C ++. Puedes pensar en ellos como un mecanismo de complemento para el lenguaje.


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 compilació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 codificación 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 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 compilación. Desafortunadamente, estás programando 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 Programación Generativa por Krysztof Czarnecki y Ulrich Eisenecker.


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


Las plantillas ofrecen cierto grado de seguridad tipo.


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

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.


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

Las macros se expanden antes de que el compilador 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.


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 Programación moderna en C ++ , de Andre Alexandrescu, o la metaprogramació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 compilador no siempre puede determinar si un nombre dependiente se refiere a un tipo o no. El uso de typename explícitamente le dice al compilador que se refiere a un tipo.

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

typename le dice al compilador 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.


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


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

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


Esta respuesta pretende arrojar luz sobre el preprocesador C y cómo se puede utilizar para la programació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 genéricos (Ver tokens Concatination ). Sin embargo, sin considerar ninguna otra característica de las plantillas de C ++, hace que todo el juego de programación genérica sea MUCHO MÁS CLARO para leer e implementar.

Si alguien quiere ver el hardcore C, solo la programación genérica en acción, lea el libevent fuente libevent ; esto también se menciona here . 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.


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 <class NumberType> NumberType add(NumberType a, NumberType b) { return a+b; } #define ADD(x, y) ((x)+(y)) } // namespace foo namespace logspace { // no problemo template <class NumberType> 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 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.