Hvad gjorde folk før skabeloner i C ++? [duplikat]

Dette spørgsmål har allerede svar her :

Kommentarer

  • dette blev sandsynligvis gjort på samme måde som i almindelig C, se Skrivning af generisk kode, når dit mål er en C-kompilator
  • @Telastyn, IIRC, skabeloner var en nyhed i CFront 3 (ca. 1990).
  • @Telastyn: omkring år 2000 leverede de fleste eksisterende C ++ – kompilatorer ikke skabeloner særlig godt (selvom de foregav at levere det, var de fleste af dem for buggy til produktionsbrug). Skabelonsupport var for det meste til understøttelse af generiske containere, men langt fra kravene til at understøtte noget som Alexandrescu ' s " Moderne C ++ design " eksempler.
  • Er du sikker på, at generering af kompileringskode blev brugt før magt skabeloner blev realiseret? Jeg var ung dengang, men jeg er under det indtryk, at der i gamle dage kun blev brugt de mest enkle slags generering af kompileringskode. Hvis du virkelig ville skrive et program til at skrive dit program, skrev du et program / script, hvis output var C kildekode, snarere end at gøre det med skabelonsprog.
  • @Joshua – Spørgsmål lukkes som duplikat, hvis de eksisterende svar på et andet spørgsmål vedrører de primære forespørgsler bag dette spørgsmål. " Præcise " matches er ikke påkrævet; pointen er at hurtigt matche OP med svar på deres spørgsmål. Med andre ord, ja, det ' er en duplikat. Når det er sagt, smager dette spørgsmål af " OMG! Hvordan eksisterede verden før …?! " hvilket ikke er ' t et frygteligt konstruktivt spørgsmål. Doc Brown påpegede i en tidligere (og nu slettet kommentar), hvordan dette fænomen kan påvirke kommentarer.

Svar

Udover void * -markøren, der er dækket i Roberts svar , blev en teknik som denne brugt (Ansvarsfraskrivelse: 20 år gammel hukommelse):

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

Hvor jeg har glemt den nøjagtige preprocessor-magi inde i collection.h, men det var noget som dette:

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

Kommentarer

  • +1 – Der er en bog af Bjarne Stroustrup hvor han fortæller historien om C ++ (fandt den for mange år siden på universitetsbiblioteket), og denne teknik blev eksplicit beskrevet som en motivation for udvikling af skabeloner.
  • Har denne teknik et navn? Måske " X-makroer " ?

Svar

traditionel måde at implementere generiske uden at have generiske (grunden til, at skabeloner blev oprettet) er at bruge en ugyldig markør.

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

I dette eksempel kode, et binært træ eller en dobbeltkoblet liste kan være repræsenteret. Fordi item indkapsler en ugyldig markør, kan enhver datatype gemmes der. Selvfølgelig skal du kende datatypen ved kørsel, så du kan kaste den tilbage til et brugbart objekt.

Kommentarer

  • Det fungerer til generisk opbevaring, men hvad med generiske funktioner? @gnat ' s makroer kan klare det, men denne frygtelige lille klasse kan ' t. Vandt ' t dette også føre til et mareridt af strenge aliasing problemer under behandling af data?
  • @MadScienceDreams Hvorfor kunne ' t anvender du dette på funktionsadresser?
  • @IdeaHat: Ja, det ville det. Men alt dette kommer fra en tid, hvor der var meget mindre vægt på at have værktøjerne til at redde programmøren fra sine egne fejl. Med andre ord skulle du være forsigtig, fordi sproget gav dig meget mere reb til at skyde dig selv i foden.
  • Du kunne kende datatypen i den anden ende af markøren ved at gemme et eller andet sted .. måske i et bord? Måske en … tabel Dette er den slags ting, som kompilatoren opsamler for os. Med andre ord, før kompilatorer håndterede skabeloner og polymorfisme, måtte vi lave vores egne.
  • @IdeaHat: For generiske funktioner skal du se på qsort i C-biblioteket. Det kan sortere alt, fordi det tager en funktionsmarkør til sammenligningsfunktionen og sender et par ugyldige * ' s.

Svar

Som andre svar påpegede, kan du bruge void* til generiske datastrukturer.For andre former for parametrisk polymorfisme blev præprocessormakroer brugt, hvis noget blev gentaget et parti (som snesevis af gange). For at være ærlig, kopierede og indsatte folk det meste af tiden for moderat gentagelse og skiftede derefter typerne, fordi der er mange faldgruber med makroer, der gør dem problematiske.

Vi har virkelig brug for en navn til det omvendte af blub paradox , hvor folk har svært ved at forestille sig programmering på et mindre udtryksfuldt sprog, fordi dette kommer meget op på dette sted. Hvis du aldrig har brugt et sprog med udtryksfulde måder at implementere parametrisk polymorfisme på, ved du ikke rigtig, hvad du mangler. Du accepterer bare kopiering og indsættelse som noget irriterende, men nødvendigt.

Der er ineffektivitet i dine nuværende valgsprog, som du endnu ikke er klar over. Om tyve år vil folk undre sig over, hvordan du eliminerede dem. Det korte svar er, at du ikke “t, fordi du ikke vidste, at du kunne.

Kommentarer

  • We really need a name for the opposite of the blub paradox, where people have a hard time imagining programming in a less expressive language, because this comes up a lot on this site. Det virker ligesom blubparadoxet for mig (" Sprog, der er mindre magtfulde end Blub, er tydeligvis mindre magtfulde, fordi de ' er mangler nogle funktioner, som han ' plejede. ") Det ' d var det modsatte hvis personen kendte sit sprog ' s begrænsninger og kunne forestille sig funktioner, der løser dem.
  • Modsat er ikke ' t ganske det ord, jeg gik efter, men det ' er det tætteste, jeg kunne komme op med. Blub-paradokset antager evnen til at genkende et mindre magtfuldt sprog, men kommenterer ikke ' om vanskelighederne med at forstå programmering i en. Noget kontraintuitivt giver evnen til at programmere på et mere kraftfuldt sprog ikke ' t en passende evne til at programmere i en mindre magtfuld.
  • Jeg formoder, at " samtale " er det ord, du ' leder efter: " En situation, objekt eller udsagn, der er det modsatte af en anden eller svarer til den, men med visse udtryk transponeret "

Svar

Jeg husker, da gcc blev leveret med genclass – et program, der som input tog et sæt parametertyper ( f.eks. nøgle og værdi for et kort) og en speciel syntaksfil, der beskrev en parametreret type (f.eks. et kort eller en vektor) og genererede en gyldig C ++ – implementering med parametertyperne udfyldt.

Så hvis du havde brug for Map<int, string> og Map<string, string> (dette var ikke den egentlige syntaks, husk at) du var nødt til kør programmet to gange for at generere noget som map_string_string.h og map_int_string.h og brug dem derefter i din kode.

Se man-siden for genclass og dokumentation fra GNU C ++ Library 2.0 for flere detaljer.

Svar

[Til OP: Jeg prøver ikke at tage dig personligt, men øge din og andres bevidsthed om at tænke på logikken i spørgsmålet ( s) spurgte om SE og andre steder. Du skal ikke tage dette personligt!]

Spørgsmålets titel er god, men du begrænser alvorligt omfanget af dine svar ved at inkludere “… situationer, hvor de havde brug for kompilering af kodegenerering. “Mange gode svar på spørgsmålet om, hvordan man opretter kompilering af kodegenerering i C ++ uden skabeloner, findes på denne side, men for at besvare det spørgsmål, du oprindeligt stillede:

Hvad gjorde folk før skabeloner i C ++?

Svaret er selvfølgelig, at de (vi) ikke brugte dem. Ja, jeg er tunge i kinden, men detaljerne i spørgsmålet i kroppen ser ud til (måske overdrevet) at antage, at alle elsker skabeloner, og at der aldrig kunne have været nogen kodning uden dem.

Som et eksempel afsluttede jeg mange mange kodningsprojekter på forskellige sprog uden at have behov for generering af kompileringskode og tror, at andre også har gjort det. Sikker på, det problem, der blev løst ved skabeloner, var en kløe, der var stor nok til, at nogen faktisk skrabet det, men scenariet fra dette spørgsmål var stort set ikke-eksisterende.

Overvej et lignende spørgsmål i biler:

Hvordan skiftede chauffører fra et gear til et andet ved hjælp af en automatiseret metode, der skiftede gear for dig, inden den automatiske transmission blev opfundet?

Spørgsmålet er selvfølgelig fjollet. At spørge, hvordan en person gjorde X, før X blev opfundet, er ikke rigtig et gyldigt spørgsmål. Svaret er generelt, “vi gjorde det ikke og savnede det ikke, fordi vi ikke vidste, at det nogensinde ville eksistere”.Ja, det er let at se fordelen efter det faktum, men at antage, at alle stod rundt og sparkede i hælene, ventede på automatisk transmission eller på C ++ – skabeloner, er virkelig ikke sandt.

På spørgsmålet, “hvordan skiftede chauffører gear, inden den automatiske transmission blev opfundet?”, Kan man med rimelighed svare “manuelt”, og det er den type svar, du får her. Det kan endda være den type spørgsmål, som du mente at stille.

Men det var ikke den, du spurgte.

Så:

Q: Hvordan brugte folk skabeloner, før skabeloner blev opfundet?

A: Vi gjorde det ikke.

Q: Hvordan brugte folk skabeloner, før skabeloner blev opfundet, når de havde brug for brug skabeloner ?

A: Vi behøvede ikke at bruge dem. Hvorfor antage, at vi gjorde det? (Hvorfor antage, at vi gør det?)

Spørgsmål: Hvad er alternative måder at opnå de resultater, som skabeloner leverer?

A: Der findes mange gode svar ovenfor.

Tænk på logiske fejl i dine indlæg, før du skriver.

[Tak! Venligst, ingen skade er beregnet her.]

Kommentarer

  • Jeg tror du ' er urimeligt pedantisk. OP udtrykte ikke sit spørgsmål så godt, som han kunne have. Men jeg synes, det ' er ret klart, at han ville spørge noget som " hvordan oprettede folk generiske funktioner før skabeloner ". Jeg tror i dette tilfælde, at den passende handling ville have været at redigere hans svar eller efterlade en kommentar snarere end at holde et ret nedlatende foredrag.

Svar

Frygtelige makroer er rigtige, fra http://www.artima.com/intv/modern2.html :

Bjarne Stroustrup: Ja. Når du siger “skabelontype T”, er det virkelig den gamle matematiske “for alle T.” Det er sådan, det betragtes. Mit allerførste papir om “C med klasser” (der udviklede sig til C ++) fra 1981 nævnte parametriserede typer. Der fik jeg problemet rigtigt, men jeg fik løsningen helt forkert. Jeg forklarede, hvordan du kan parametrere typer med makroer og dreng, der var elendig kode.

Du kan se, hvordan en gammel makroversion af en skabelon blev brugt her : http://www.xvt.com/sites/default/files/docs/Pwr++_Reference/rw/docs/html/toolsref/rwgvector.html

Svar

Som Robert Harvey allerede sagde, er en ugyldig markør den generiske datatype.

Et eksempel fra standard C-biblioteket, hvordan man sorterer en matrix af dobbelt med en generisk sortering:

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

Hvor compare_double er defineret som:

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

Signaturen til qsort er defineret i stdlib.h:

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

Bemærk, at der ikke er nogen type kontrol ved kompileringstid, ikke engang ved kørselstid. Hvis du sorterer en liste over strenge med komparatoren ovenfor, der forventer fordobling, vil den med glæde forsøge at fortolke den binære repræsentation af en streng som en dobbelt og sortere i overensstemmelse hermed.

Svar

En måde at gøre dette på er sådan:

https://github.com/rgeminas/gp–/blob/master/src/scope/darray.h

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

DARRAY_TYPEDECL makroen opretter effektivt en strukturdefinition (i en enkelt linje), der erstatter name med det navn, du videregiver, og lagring af en matrix af type, du sender (name er der, så du kan sammenkæde det til basisstrukturnavnet og stadig har en gyldig identifikator – darray_int * er ikke et gyldigt navn for en struktur), mens DARRAY_IMPL makroen definerer de funktioner, der fungerer på den pågældende struktur (i så fald er de “markeret statisk, så man ville kun kalde definitionen en gang og ikke adskille alt).

Dette ville blive brugt som:

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

Svar

Jeg tror, skabeloner bliver brugt meget som en måde at genbruge containertyper, der har en mange algoritmiske værdier som dynamiske arrays (vektorer), kort, træer osv. sortering osv.

Uden skabeloner er disse nødvendigvis implementeringer skrevet på en generisk måde og får lige nok information om den type, der kræves for deres domæne. For eksempel, med en vektor, har de bare brug for dataene, der skal være i stand til, og de har brug for at vide størrelsen på hvert element.

Lad os sige, at du har en containerklasse kaldet Vector, der gør dette. Det er ugyldigt *. Den forenklede anvendelse af dette ville være, at applikationslagskoden udførte meget casting. Så hvis de administrerer Cat-genstande, skal de kaste Cat * til ugyldighed * og tilbage overalt. Littering af applikationskode med kaster har åbenlyse problemer.

Skabeloner løser dette.

En anden måde at løse den på er at oprette en brugerdefineret containertype til den type, du gemmer i containeren. Så hvis du har en Cat-klasse, opretter du en CatList-klasse afledt af Vector.Du overbelaster derefter de få metoder, du bruger, og introducerer versioner, der tager Cat-objekter i stedet for ugyldige *. Så du overbelaster Vector :: Add (void *) metoden med Cat :: Add (Cat *), som internt blot overfører parameteren til Vector :: Add (). Derefter kalder du i din applikationskode overbelastet version af Tilføj, når du passerer i en Cat-genstand, og undgå således at kaste. For at være retfærdig kræver Add-metoden ikke en rollebesætning, fordi et Cat * -objekt konverteres til ugyldigt * uden et cast. Men metoden til at hente et element, såsom indeksoverbelastning eller en Get () -metode ville være.

Den anden tilgang, det eneste eksempel, som jeg husker fra begyndelsen af 90erne med en stor applikationsramme, er at bruge et brugerdefineret værktøj, der opretter disse typer over klasser. Jeg tror MFC gjorde dette. De havde forskellige klasser til containere som CStringArray, CRectArray, CDoulbeArray, CIntArray osv. I stedet for at opretholde duplikatkode udførte de en eller anden form for metaprogrammering svarende til makroer ved hjælp af et eksternt værktøj, der ville generere klasserne. De gjorde værktøjet tilgængeligt med Visual C ++ i tilfælde af nogen ville bruge det – det gjorde jeg aldrig. Måske skulle jeg have det. Men på det tidspunkt udråbte eksperterne “Sane subset of C ++” og “Du behøver ikke skabeloner”

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *