Cosa facevano le persone prima dei template in C ++? [duplicare]

42

Non sono nuovo alla programmazione, ma ne sono uno iniziato alcuni anni fa e amo i modelli.

Ma nei tempi precedenti, in che modo le persone si occupavano di situazioni in cui avevano bisogno di generare codice in fase di compilazione come i modelli? Sto indovinando macro orribili e orribili (almeno questo è come lo farei io), ma la ricerca su google della domanda sopra riportata mi porta solo pagine e pagine di tutorial modello.

Esistono molti argomenti contro l'utilizzo di modelli e, mentre in genere si riducono alla leggibilità, " YAGNI ", e lamentandosi di quanto mal implementato, non ci sono molte alternative sulle alternative con potere simile. Quando io faccio devo fare una specie di generici in fase di compilazione e do voglio mantenere il mio codice DRY , come si fa / si evita di usare i template?

    
posta IdeaHat 12.11.2014 - 16:56
fonte

9 risposte

48

Oltre al puntatore void * che è coperto nella risposta di Robert , è stata utilizzata una tecnica come questa (Disclaimer: Memoria di 20 anni):

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

Dove ho dimenticato l'esatta magia del preprocessore all'interno di collection.h, ma era qualcosa del genere:

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
    
risposta data 12.11.2014 - 20:13
fonte
44

Il modo tradizionale di implementare i generici senza avere generici (il motivo per cui i template sono stati creati) è usare un puntatore vuoto.

typedef struct Item{ 
        void* data;
    } Item;

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

In questo codice di esempio, è possibile rappresentare un albero binario o un elenco a doppio collegamento. Poiché item incapsula un puntatore vuoto, qualsiasi tipo di dati può essere memorizzato lì. Ovviamente, dovresti conoscere il tipo di dati in fase di esecuzione in modo da poterlo ritrasformare su un oggetto utilizzabile.

    
risposta data 12.11.2014 - 17:21
fonte
16

Come indicato da altre risposte, è possibile utilizzare void* per strutture di dati generiche. Per altri tipi di polimorfismo parametrico, sono state utilizzate macro del preprocessore se qualcosa ha ripetuto un lotto (come dozzine di volte). Ad essere onesti, però, il più delle volte per una moderata ripetizione, le persone hanno appena copiato e incollato, quindi modificato i tipi, perché ci sono molte insidie con macro che li rendono problematici.

Abbiamo davvero bisogno di un nome per il contrario del blub paradosso , in cui le persone hanno difficoltà a immaginare di programmare in un linguaggio meno espressivo, perché questo appare molto su questo sito. Se non hai mai usato un linguaggio con metodi espressivi per implementare il polimorfismo parametrico, non sai veramente cosa ti stai perdendo. Accettate semplicemente che copiare e incollare sia un po 'fastidioso, ma necessario.

Ci sono inefficienze nei tuoi attuali linguaggi di scelta di cui non sei ancora a conoscenza. Tra vent'anni le persone si staranno chiedendo come li hai eliminati. La risposta breve è che non l'hai fatto, perché non sapevi che potevi.

    
risposta data 12.11.2014 - 18:07
fonte
8

Ricordo quando gcc è stato spedito con genclass - un programma che ha preso come input un insieme di tipi di parametri (es. chiave e valore per una mappa) e un file di sintassi speciale che descriveva un tipo parametrizzato (ad esempio una mappa o un Vector) e generato implementazioni C ++ valide con i tipi param compilati.

Quindi se avevi bisogno di Map<int, string> e Map<string, string> (questa non era la sintassi attuale, bada bene) dovevi eseguire quel programma due volte per generare qualcosa come map_string_string.h e map_int_string.h e poi usarli nel tuo codice .

Ecco la pagina man per genclass : link

    
risposta data 13.11.2014 - 12:26
fonte
7

[All'OP: Non sto cercando di prenderti sul personale, ma sensibilizzare te e gli altri sul modo di pensare alla logica delle domande poste su SE e altrove. Per favore, non prenderlo personalmente!]

Il titolo della domanda è buono, ma limiti severamente l'ambito delle tue risposte includendo "... situazioni in cui avevano bisogno della generazione del codice in fase di compilazione". Molte risposte valide alla domanda su come eseguire la generazione di codice in fase di compilazione in C ++ senza modelli esistono in questa pagina, ma per rispondere alla domanda che hai posto originariamente:

Che cosa facevano le persone prima dei template in C ++?

La risposta è, naturalmente, loro (noi) non li usiamo. Sì, sono ironico, ma i dettagli della domanda nel corpo sembrano (forse esageratamente) presupporre che tutti amano i modelli e che nessuna codifica potrebbe mai essere stata fatta senza di loro.

Ad esempio, ho completato molti progetti di codifica in varie lingue senza la necessità di generare codice in fase di compilazione, e credo che anche altri lo abbiano fatto. Certo, il problema risolto dai modelli era un prurito abbastanza grande che qualcuno lo ha effettivamente graffiato, ma lo scenario posto da questa domanda era, in gran parte, inesistente.

Considera una domanda simile nelle automobili:

In che modo i piloti si sono spostati da una marcia all'altra, utilizzando un metodo automatico che ha cambiato marcia per te, prima che la trasmissione automatica fosse inventata?

La domanda è, ovviamente, sciocca. Chiedere come una persona abbia fatto X prima dell'invenzione di X non è una domanda valida. La risposta è generalmente "non l'abbiamo fatto e non ci siamo persi perché non sapevamo che sarebbe mai esistito". Sì, è facile vedere il vantaggio dopo il fatto, ma presumere che tutti stessero in piedi, a calci, aspettando la trasmissione automatica, o per i modelli C ++, non è proprio vero.

Alla domanda, "in che modo i conducenti hanno cambiato marcia prima dell'invenzione della trasmissione automatica?" si può ragionevolmente rispondere, 'manualmente', e questo è il tipo di risposte che si ottengono qui. Potrebbe anche essere il tipo di domanda che intendevi porre.

Ma non è stato quello che hai chiesto.

D: In che modo le persone hanno utilizzato i modelli prima dell'invenzione dei modelli?

A: Non l'abbiamo fatto.

D: In che modo le persone hanno utilizzato i modelli prima dell'invenzione dei modelli, quando avevano bisogno di usare i modelli ?

A: Noi non abbiamo bisogno di usarli. Perché supponiamo di averlo fatto? (Perché supponiamo di farlo?)

D: Quali sono i modi alternativi per ottenere i risultati forniti dai modelli?

A: Molte risposte positive esistono sopra.

Per favore, pensa a errori logici nei tuoi post prima di pubblicare.

[Grazie! Per favore, nessun danno inteso qui.]

    
risposta data 13.11.2014 - 01:27
fonte
6

Le macro orribili sono corrette, dal link :

Bjarne Stroustrup: Yes. When you say, "template type T," that is really the old mathematical, "for all T." That's the way it's considered. My very first paper on "C with Classes" (that evolved into C++) from 1981 mentioned parameterized types. There, I got the problem right, but I got the solution totally wrong. I explained how you can parameterize types with macros, and boy that was lousy code.

Puoi vedere come è stata usata una vecchia versione macro di un modello: link

    
risposta data 12.11.2014 - 19:10
fonte
5

Come ha già detto Robert Harvey, un puntatore vuoto è il tipo di dati generico.

Un esempio della libreria C standard, come ordinare una matrice di double con un ordinamento generico:

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

Dove compare_double è definito come:

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 di qsort è definita in stdlib.h:

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

Si noti che non c'è alcun tipo di verifica in fase di compilazione, nemmeno in fase di esecuzione. Se si ordina una lista di stringhe con il comparatore sopra che si aspetta il doppio, tenterà felicemente di interpretare la rappresentazione binaria di una stringa come una doppia e ordinarla di conseguenza.

    
risposta data 12.11.2014 - 17:44
fonte
1

Un modo per farlo è questo:

link

#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 in modo efficace una definizione di struct (in una singola riga), sostituendo name con il nome che si passa e memorizzando una matrice di type che si passa (il name è lì in modo da poter concatenarlo al nome della struct di base e avere ancora un identificatore valido - darray_int * non è un nome valido per una struct), mentre la macro DARRAY_IMPL definisce le funzioni che operano su quella struct (in tal caso sono marcate static solo in modo che uno chiamerebbe la definizione solo una volta e non separerebbe tutto).

Questo sarebbe usato come:

#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();
    
risposta data 14.11.2014 - 14:40
fonte
1

Penso che i template si usino molto come un modo per riutilizzare i tipi di container che hanno molti valori algoritmici come array dinamici (vettori), mappe, alberi, ecc., ordinamento, ecc.

Senza modelli, necessariamente, questi contengono implementazioni scritte in modo generico e vengono fornite solo informazioni sufficienti sul tipo richiesto per il loro dominio. Ad esempio, con un vettore, hanno solo bisogno dei dati per essere chiari e devono conoscere la dimensione di ciascun elemento.

Supponiamo che tu abbia una classe contenitore chiamata Vector che esegue questa operazione. Prende il vizio *. L'uso semplicistico di questo sarebbe che il codice del livello dell'applicazione esegua molto casting. Quindi, se gestiscono oggetti Cat, devono lanciare Cat * per annullare * e tornare indietro. Il codice delle applicazioni di lattazione con cast ha evidenti problemi.

I modelli risolvono questo.

Un altro modo per risolverlo è creare un tipo di contenitore personalizzato per il tipo che stai memorizzando nel contenitore. Quindi, se hai una classe Cat, devi creare una classe CatList derivata da Vector. Quindi sovraccarichi i pochi metodi che usi, introducendo versioni che prendono oggetti Cat anziché void *. Quindi dovresti sovraccaricare il metodo Vector :: Add (void *) con Cat :: Add (Cat *), che internamente passa semplicemente il parametro a Vector :: Add (). Quindi, nel codice dell'applicazione, chiamereste la versione sovraccaricata di Aggiungi quando passate in un oggetto Cat e quindi evitate il casting. Per essere onesti, il metodo Aggiungi non richiede un cast perché un oggetto Cat * converte in void * senza cast. Ma il metodo per recuperare un elemento, ad esempio l'overload dell'indice o un metodo Get (), sarebbe.

L'altro approccio, l'unico esempio di cui ricordo i primi anni '90 con un framework per applicazioni di grandi dimensioni, è l'utilizzo di un'utility personalizzata che crea questi tipi su classi. Credo che l'MFC abbia fatto questo. Avevano classi diverse per contenitori come CStringArray, CRectArray, CDoulbeArray, CIntArray, ecc. Piuttosto che mantenere il codice duplicato, hanno fatto un tipo di meta-programmazione simile alle macro, usando uno strumento esterno che avrebbe generato le classi. Hanno reso lo strumento disponibile con Visual C ++ nel caso in cui qualcuno volesse usarlo - non l'ho mai fatto. Forse dovrei avere. Ma a quel tempo, gli esperti stavano propagandando "Sane sottoinsieme di C ++" e "Non hai bisogno di modelli"

    
risposta data 12.11.2014 - 18:23
fonte

Leggi altre domande sui tag