¿Qué hicieron las personas antes de las plantillas en C ++? [duplicar]

42

No soy nuevo en programación, pero soy uno que comenzó hace unos años y me encantan las plantillas.

Pero en los tiempos anteriores, ¿cómo lidiaban las personas con las situaciones en las que necesitaban la generación de código en tiempo de compilación como las plantillas? Estoy adivinando macros horribles, horribles (al menos así es como lo haría), pero buscar en Google la pregunta anterior solo me permite obtener páginas y páginas de tutoriales de plantillas.

Hay muchos argumentos en contra del uso de plantillas, y mientras que generalmente se reduce a legibilidad, " YAGNI ", y quejándose de lo mal que se implementa, no hay mucho por ahí sobre las alternativas con un poder similar. Cuando do necesito hacer algún tipo de genéricos en tiempo de compilación y do quiero mantener mi código DRY , ¿cómo evita / se evita usar plantillas?

    
pregunta IdeaHat 12.11.2014 - 16:56

9 respuestas

48

Además del puntero void * que está cubierto en la respuesta de Robert , se utilizó una técnica como esta (Exención de responsabilidad: Memoria de 20 años):

#define WANTIMP

#define TYPE int
#include "collection.h"
#undef TYPE

#define TYPE string
#include "collection.h"
#undef TYPE

int main() {
    Collection_int lstInt;
    Collection_string lstString;
}

Donde he olvidado la magia del preprocesador exacto dentro de collection.h, pero fue algo como esto:

class Collection_ ## TYPE {
public:
 Collection() {}
 void Add(TYPE value);
private:
 TYPE *list;
 size_t n;
 size_t a;
}

#ifdef WANTIMP
void Collection_ ## TYPE ::Add(TYPE value)
#endif
    
respondido por el Joshua 12.11.2014 - 20:13
44

La forma tradicional de implementar genéricos sin tener genéricos (la razón por la que se crearon las plantillas) es usar un puntero de vacío.

typedef struct Item{ 
        void* data;
    } Item;

typedef struct Node{
    Item Item; 
    struct Node* next; 
    struct Node* previous;
} Node;

En este código de ejemplo, se puede representar un árbol binario o una lista con doble enlace. Debido a que item encapsula un puntero de vacío, cualquier tipo de datos puede almacenarse allí. Por supuesto, tendría que conocer el tipo de datos en tiempo de ejecución para poder volver a convertirlo en un objeto utilizable.

    
respondido por el Robert Harvey 12.11.2014 - 17:21
16

Como indicaron otras respuestas, puede usar void* para estructuras de datos genéricos. Para otros tipos de polimorfismo paramétrico, las macros del preprocesador se utilizaron si algo se repetía un lote (como docenas de veces). Sin embargo, para ser honesto, la mayoría de las veces, para una repetición moderada, las personas simplemente copiaron y pegaron, luego cambiaron los tipos, porque hay muchas trampas con las macros que las hacen problemáticas.

Realmente necesitamos un nombre para el converso de blub paradox , donde la gente tiene dificultades para imaginar la programación en un lenguaje menos expresivo, porque esto aparece mucho en este sitio. Si nunca ha usado un lenguaje con formas expresivas de implementar el polimorfismo paramétrico, realmente no sabe lo que se está perdiendo. Simplemente aceptas que copiar y pegar es algo molesto, pero necesario.

Hay ineficiencias en los idiomas actuales de su elección que ni siquiera conoce. En veinte años la gente se preguntará cómo los eliminó. La respuesta corta es que no lo hiciste, porque no sabías que podías.

    
respondido por el Karl Bielefeldt 12.11.2014 - 18:07
8

Recuerdo cuando gcc se envió con genclass , un programa que tomó como entrada un conjunto de tipos de parámetros (por ejemplo, clave y valor para un mapa) y un archivo de sintaxis especial que describía un tipo parametrizado (por ejemplo, un mapa o un Vector) y generó implementaciones válidas de C ++ con los tipos param rellenados.

Entonces, si necesitabas Map<int, string> y Map<string, string> (esta no era la sintaxis real, ten en cuenta que) tenías que ejecutar ese programa dos veces para generar algo como map_string_string.h y map_int_string.h y luego usarlos en tu código .

Aquí está la página del manual para genclass : enlace

    
respondido por el Rafał Dowgird 13.11.2014 - 12:26
7

[Para el OP: No estoy tratando de molestarlo personalmente, sino que lo hago más consciente de lo que piensan los demás acerca de la lógica de la (s) pregunta (s) sobre el SE y otros lugares. Por favor, no tome esto personalmente!

El título de la pregunta es bueno, pero está limitando severamente el alcance de sus respuestas al incluir '... situaciones en las que necesitaban generación de código en tiempo de compilación'. En esta página existen muchas buenas respuestas a la pregunta sobre cómo realizar la generación de código en tiempo de compilación en C ++ sin plantillas, pero para responder a la pregunta que usted formuló originalmente:

¿Qué hicieron las personas antes de las plantillas en C ++?

La respuesta es, por supuesto, ellos (nosotros) no los usamos. Sí, estoy siendo irónico, pero los detalles de la pregunta en el cuerpo parecen (quizás exageradamente) suponer que a todos les encantan las plantillas y que no se podría haber codificado sin ellas.

Como ejemplo, completé muchos proyectos de codificación en varios idiomas sin necesidad de generar código de tiempo de compilación, y creo que otros también lo han hecho. Claro, el problema resuelto por las plantillas era una picazón lo suficientemente grande como para que alguien realmente lo rascara, pero el escenario planteado por esta pregunta era, en gran medida, inexistente.

Considera una pregunta similar en autos:

¿Cómo cambiaron los conductores de una marcha a otra, utilizando un método automatizado que cambió las marchas para usted antes de que se inventara la transmisión automática?

La pregunta es, por supuesto, tonta. Preguntar cómo hizo una persona X antes de que se inventara X no es realmente una pregunta válida. La respuesta es, en general, "no lo hicimos y no lo perdimos porque no sabíamos que existiría". Sí, es fácil ver el beneficio después del hecho, pero suponer que todos estaban parados, pateándose, esperando la transmisión automática o las plantillas de C ++, realmente no es cierto.

A la pregunta, '¿cómo cambiaron de marcha los conductores antes de que se inventara la transmisión automática?' Uno puede responder razonablemente, 'manualmente', y ese es el tipo de respuestas que está obteniendo aquí. Incluso puede ser el tipo de pregunta que quisiste hacer.

Pero no fue a quien le preguntaste.

Entonces:

P: ¿Cómo utilizaban las personas las plantillas antes de que se inventaran las plantillas?

A: No lo hicimos.

P: ¿Cómo utilizaban las personas las plantillas antes de que se inventaran las plantillas, cuando necesitaban usar plantillas ?

A: no necesitamos usarlos. ¿Por qué asumir que lo hicimos? (¿Por qué suponemos que lo hacemos?)

P: ¿Cuáles son las formas alternativas de lograr los resultados que proporcionan las plantillas?

A: Muchas buenas respuestas existen arriba.

Por favor, piensa en las falacias lógicas en tus publicaciones antes de publicar.

[Gracias! Por favor, no intentes hacer daño aquí.]

    
respondido por el Joseph Cheek 13.11.2014 - 01:27
6

Las macros horribles están bien, de enlace :

  

Bjarne Stroustrup: Sí. Cuando dices, "tipo de plantilla T", eso es realmente el antiguo matemático, "para todas las T." Esa es la forma en que se considera. Mi primer artículo sobre "C con clases" (que evolucionó a C ++) de 1981 mencionó tipos parametrizados. Ahí, tengo el problema correcto, pero tengo la solución totalmente equivocada. Le expliqué cómo puede parametrizar tipos con macros, y chico que era un código pésimo.

Puede ver cómo se usó una versión de macro antigua de una plantilla aquí: enlace

    
respondido por el jas 12.11.2014 - 19:10
5

Como Robert Harvey ya dijo, un puntero de vacío es el tipo de datos genérico.

Un ejemplo de la biblioteca estándar de C, cómo ordenar una matriz de doble con una clasificación genérica:

double *array = ...;
int size = ...;   
qsort (array, size, sizeof (double), compare_doubles);

Donde compare_double se define como:

int compare_doubles (const void *a, const void *b)
{
    const double *da = (const double *) a;
    const double *db = (const double *) b;
    return (*da > *db) - (*da < *db);
}

La firma de qsort se define en stdlib.h:

void qsort(void *base, size_t nmemb, size_t size,
    int (*compar)(const void *, const void *)
);

Tenga en cuenta que no hay comprobación de tipos en el momento de la compilación, ni siquiera en el tiempo de ejecución. Si ordena una lista de cadenas con el comparador anterior que espera que se duplique, con mucho gusto tratará de interpretar la representación binaria de una cadena como un doble y se ordenará en consecuencia.

    
respondido por el Florian F 12.11.2014 - 17:44
1

Una forma de hacer esto es así:

enlace

#define DARRAY_DEFINE(name, type) DARRAY_TYPEDECL(name, type) DARRAY_IMPL(name, type)

// This is one single long line
#define DARRAY_TYPEDECL(name, type) \
typedef struct darray_##name \
{ \
    type* base; \
    size_t allocated_mem; \
    size_t length; \
} darray_##name; 

// This is also a single line
#define DARRAY_IMPL(name, type) \
static darray_##name* darray_init_##name() \
{ \
    darray_##name* arr = (darray_##name*) malloc(sizeof(darray_##name)); \
    arr->base = (type*) malloc(sizeof(type)); \
    arr->length = 0; \
    arr->allocated_mem = 1; \
    return arr; \
}

La macro DARRAY_TYPEDECL crea efectivamente una definición de estructura (en una sola línea), reemplazando name con el nombre que pasa, y almacenando una matriz del type que pasa (el name está allí para que pueda concaténelo al nombre de la estructura base y aún tenga un identificador válido - darray_int * no es un nombre válido para una estructura), mientras que la macro DARRAY_IMPL define las funciones que operan en esa estructura (en ese caso, están marcadas como estáticas solo para que uno solo llamaría a la definición una vez y no separaría todo).

Esto se usaría como:

#include "darray.h"
// No types have been defined yet
DARRAY_DEFINE(int_ptr, int*)

// by this point, the type has been declared and its functions defined
darray_int_ptr* darray = darray_int_ptr_init();
    
respondido por el Renan Gemignani 14.11.2014 - 14:40
1

Creo que las plantillas se utilizan mucho como una forma de reutilizar tipos de contenedores que tienen muchos valores algorítmicos como matrices dinámicas (vectores), mapas, árboles, etc. clasificación, etc.

Sin plantillas, necesariamente, estas implementaciones de contenido se escriben de una manera genérica y se les proporciona información suficiente sobre el tipo requerido para su dominio. Por ejemplo, con un vector, solo necesitan que los datos sean compatibles y necesitan saber el tamaño de cada elemento.

Digamos que tienes una clase contenedora llamada Vector que hace esto. Se lleva el vacío *. El uso simplista de esto sería que el código de la capa de la aplicación haga mucho casting. Así que si están administrando objetos Cat, tienen que lanzar Cat * para anular * y volver a todo el lugar. El código de aplicación de basura con moldes tiene problemas obvios.

Las plantillas resuelven esto.

Otra forma de resolverlo es crear un tipo de contenedor personalizado para el tipo que está almacenando en el contenedor. Entonces, si tienes una clase Cat, crearías una clase CatList derivada de Vector. Luego, sobrecarga los pocos métodos que utiliza, introduciendo versiones que toman objetos Cat en lugar de void *. Así que sobrecargaría el método Vector :: Add (void *) con Cat :: Add (Cat *), que internamente simplemente pasa el parámetro a Vector :: Add (). Luego, en el código de su aplicación, llamaría a la versión sobrecargada de Agregar cuando pase un objeto Cat y así evitaría la conversión. Para ser justos, el método Add no requeriría un lanzamiento porque un objeto Cat * se convierte en vacío * sin un lanzamiento. Pero el método para recuperar un elemento, como la sobrecarga del índice o un método Get (), lo haría.

El otro enfoque, el único ejemplo que recuerdo de la década de los 90 con un gran marco de aplicación, es usar una utilidad personalizada que crea estos tipos en clases. Creo que MFC hizo esto. Tenían diferentes clases para contenedores como CStringArray, CRectArray, CDoulbeArray, CIntArray, etc. En lugar de mantener un código duplicado, hicieron algún tipo de meta-programación similar a las macros, usando una herramienta externa que generaría las clases. Hicieron la herramienta disponible con Visual C ++ en caso de que alguien quisiera usarla, nunca lo hice. Tal vez debería haberlo hecho. Pero en ese momento, los expertos estaban promocionando, "Sane subconjunto de C ++" y "No necesita plantillas"

    
respondido por el zumalifeguard 12.11.2014 - 18:23

Lea otras preguntas en las etiquetas