Vad gjorde folk innan mallar i C ++? [duplicera]

<åt sidan class = "s-notice s-notice__info js-post-notice mb16" role = "status">

Denna fråga har redan svar här :

Kommentarer

  • detta gjordes troligen på samma sätt som i vanlig C, se Att skriva generisk kod när ditt mål är en C-kompilator
  • @Telastyn, IIRC, mallar var en nyhet för CFront 3 (cirka 1990).
  • @Telastyn: omkring år 2000 tillhandahöll de flesta befintliga C ++ – kompilatorer inte mallar särskilt bra (även om de låtsades ge dem, var de flesta för buggiga för produktionsanvändning). Mallstöd var mestadels för att stödja generiska behållare, men långt ifrån kraven för att stödja något som Alexandrescu ' s " Modern C ++ design " exempel.
  • Är du säker på att generering av kompileringskod användes innan mallarnas kraft förverkligades? Jag var ung då, men jag har intrycket av att förr i tiden bara de enklaste typerna av generering av kompileringskod användes. Om du verkligen ville skriva ett program för att skriva ditt program, skrev du ett program / skript vars utdata var C-källkod, snarare än att göra det med mallens språk.
  • @Joshua – Frågor stängs som duplikat om de befintliga svaren på en annan fråga behandlar de primära frågorna bakom den här frågan. " Exakt " matchningar krävs inte; poängen är att snabbt matcha OP med svar på deras fråga. Med andra ord, ja, det ' är en duplikat. Som sagt, den här frågan slår av " OMG! Hur existerade världen tidigare …?! " vilket inte är ' t en fruktansvärt konstruktiv fråga. Doc Brown påpekade i en tidigare (och nu borttagen kommentar) hur detta fenomen kan påverka kommentarer.

Svar

Förutom void * -pekaren som täcks i Roberts svar användes en teknik som denna (Ansvarsfriskrivning: 20 år gammalt minne):

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

Där jag har glömt den exakta förprocessormagiken inuti collection.h, men det var något så här:

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 – Det finns en bok av Bjarne Stroustrup där han berättar historien om C ++ (hittade den för flera år sedan på universitetsbiblioteket), och denna teknik beskrivs uttryckligen som en motivering för att utveckla mallar.
  • Har den här tekniken ett namn? = ”e68d2f6be7″>

" X-makron " ?

Svar

traditionellt sätt att implementera generics utan att ha generics (anledningen till att mallarna skapades) är att använda en tom pekare.

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

I detta exempel kod, ett binärt träd eller en dubbelt länkad lista kan representeras. Eftersom item inkapslar en tom pekare kan vilken datatyp som helst lagras där. Naturligtvis måste du känna till datatypen vid körning så att du kan kasta tillbaka den till ett användbart objekt.

Kommentarer

  • Det fungerar för generisk lagring, men hur är det med generiska funktioner? @gnat ' s makron kan hantera det, men den här hemska lilla klassen kan ' t. Vann ' t detta också leda till en mardröm av strikta aliasing problem vid bearbetning av data?
  • @MadScienceDreams Varför kunde inte ' t använder du detta på funktionsadresser?
  • @IdeaHat: Ja, det skulle det. Men allt detta kommer från en tid då det var mycket mindre betoning på att verktygen skulle rädda programmeraren från sina egna misstag. Med andra ord måste du vara försiktig eftersom språket gav dig mycket mer rep för att skjuta dig själv i foten.
  • Du kunde känna till datatypen i andra änden av pekaren genom att lagra någonstans .. … kanske i ett bord? Kanske en … vtabell? Det här är den typ av saker som kompilatorn tar bort för oss. Med andra ord, innan kompilatorer hanterade mallar och polymorfism, var vi tvungna att göra våra egna.
  • @IdeaHat: För generiska funktioner, se qsort i C-biblioteket. Det kan sortera vad som helst eftersom det tar en funktionspekare för jämförelsefunktionen och skickar ett par tomrum * ' s.

Svar

Som andra svar påpekat kan du använda void* för generiska datastrukturer.För andra typer av parametrisk polymorfism användes förprocessormakron om något upprepades mycket (som dussintals gånger). För att vara ärlig dock, för det mesta för måttlig upprepning, kopierade folk bara och klistrade in och ändrade sedan typerna, för det finns många fallgropar med makron som gör dem problematiska.

Vi behöver verkligen en namn för det omvända av blub paradox , där människor har svårt att föreställa sig programmering på ett mindre uttrycksfullt språk, eftersom detta kommer upp mycket på den här webbplatsen. Om du aldrig har använt ett språk med uttrycksfulla sätt att implementera parametrisk polymorfism vet du inte riktigt vad du saknar. Du accepterar bara att kopiering och klistra in är något irriterande men nödvändigt.

Det finns ineffektivitet i dina nuvarande språk som du inte ens känner till ännu. Om tjugo år undrar människor hur du eliminerade dem. Det korta svaret är att du inte visste att du inte visste att du kunde.

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 verkar som exakt blubparadoxen för mig (" Språk som är mindre kraftfulla än Blub är uppenbarligen mindre kraftfulla eftersom de ' är saknar någon funktion som han ' brukade. ") Det ' d är motsatsen om personen kände sitt språk ' s begränsningar och kunde tänka sig funktioner som löser dem.
  • Motsatt är inte ' t ganska ordet jag tänkte på, men det ' är det närmaste jag kunde komma på. Blubparadoxen förutsätter förmågan att känna igen ett mindre kraftfullt språk, men kommenterar inte ' om svårigheten att förstå programmering i ett. Något kontraintuitivt ger förmågan att programmera på ett kraftfullare språk inte ' t en motsvarande förmåga att programmera i en mindre kraftfull.
  • Jag misstänker att " konversera " är ordet du ' letar efter: " En situation, ett objekt eller uttalande som är det motsatta av en annan eller motsvarar det men med vissa termer transponerade "

Svar

Jag minns när gcc levererades med genclass – ett program som tog som en ingång en uppsättning parametertyper ( t.ex. nyckel och värde för en karta) och en speciell syntaxfil som beskrev en parametrerad typ (säg en karta eller en vektor) och genererade en giltig C ++ – implementering med param-typerna fyllda.

Så om du behövde Map<int, string> och Map<string, string> (detta var inte den faktiska syntaxen, kom ihåg att) du var tvungen att kör det programmet två gånger för att generera något som map_string_string.h och map_int_string.h och använd sedan dessa i din kod.

Se mansidan för genclass och dokumentation från GNU C ++ Library 2.0 för mer information.

Svar

[Till OP: Jag försöker inte ta dig personligen utan höja din och andras medvetenhet om att tänka på logik i frågan ( s) frågade på SE och på andra håll. Ta det inte personligen!]

Frågan är bra, men du begränsar allvarligt omfattningen av dina svar genom att inkludera ”… situationer där de behövde generera kodkod. ”Många bra svar på frågan om hur man skapar kompilering av kodgenerering i C ++ utan mallar finns på den här sidan, men för att svara på den fråga du ursprungligen ställde:

Vad gjorde folk innan mallar i C ++?

Svaret är naturligtvis att de (vi) inte använde dem. Ja, jag håller mig tungt, men detaljerna i frågan i kroppen verkar (kanske överdrivet) utgå från att alla älskar mallar och att ingen kodning någonsin kunde ha gjorts utan dem.

Som ett exempel slutförde jag många många kodningsprojekt på olika språk utan att behöva generera kompileringskod och tror att andra också har gjort det. Visst, problemet som löstes med mallar var en klåda som var tillräckligt stor för att någon faktiskt skrapade på den, men scenariot som ställs av denna fråga var till stor del obefintligt.

Tänk på en liknande fråga i bilar:

Hur växlade förare från en växel till en annan, med en automatisk metod som växlade växlar åt dig innan den automatiska växellådan uppfanns?

Frågan är naturligtvis dum. Att fråga hur en person gjorde X innan X uppfanns är inte riktigt en giltig fråga. Svaret är generellt, ”vi gjorde det inte och missade inte det eftersom vi inte visste att det någonsin skulle existera”.Ja, det är lätt att se nyttan efter det, men att anta att alla stod och sparkade i hälen, väntade på automatisk transmission eller på C ++ – mallar, är verkligen inte sant.

På frågan ”hur växlade förare växlar innan den automatiska växellådan uppfanns?” Kan man rimligen svara, ”manuellt” och det är den typ av svar du får här. Det kan till och med vara den typ av fråga du tänkte ställa.

Men det var inte den du ställde.

Så:

F: Hur använde människor mallar innan mallar uppfanns?

A: Vi gjorde inte.

F: Hur använde folk mallar innan mallar uppfanns, när de behövde använda mallar ?

A: Vi behövde inte använda dem. Varför anta att vi gjorde det? (Varför anta att vi gör det?)

F: Vilka är alternativa sätt att uppnå de resultat som mallarna ger?

A: Många bra svar finns ovan.

Tänk på logiska felaktigheter i dina inlägg innan du lägger upp.

[Tack! Snälla, ingen skada är avsedd här.]

Kommentarer

  • Jag tror att du ' är onödigt pedantisk. OP uttryckte inte hans fråga så bra som han kunde ha. Men jag tror att det ' är ganska klart att han ville fråga något som " hur skapade folk generiska funktioner tidigare mallar ". Jag tror att i det här fallet skulle den lämpliga åtgärden ha varit att redigera hans svar eller lämna en kommentar snarare än att hålla en ganska nedlåtande föreläsning.

Svar

Hemska makron är rätt, från http://www.artima.com/intv/modern2.html :

Bjarne Stroustrup: Ja. När du säger ”malltyp T” är det verkligen den gamla matematiska ”för alla T.” Det är så det anses. Mitt allra första papper om ”C med klasser” (som utvecklades till C ++) från 1981 nämnde parametrerade typer. Där fick jag rätt problem, men jag fick lösningen helt fel. Jag förklarade hur du kan parametrisera typer med makron och pojken som var usel kod.

Du kan se hur en gammal makroversion av en mall användes här : http://www.xvt.com/sites/default/files/docs/Pwr++_Reference/rw/docs/html/toolsref/rwgvector.html

Svar

Som Robert Harvey redan sa, är en tomrumspekare den generiska datatypen.

Ett exempel från C-biblioteket, hur man sorterar en matris med dubbel med en generisk sortering:

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

Där compare_double definieras 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 för qsort definieras i stdlib.h:

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

Observera att det inte finns någon typ kontroll vid sammanställningstid, inte ens vid körningstid. Om du sorterar en lista med strängar med komparatorn ovan som förväntar sig dubbla, försöker den gärna tolka den binära representationen av en sträng som en dubbel och sortera därefter.

Svar

Ett sätt att göra detta är så här:

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

Makrot DARRAY_TYPEDECL skapar effektivt en strukturdefinition (i en rad) och ersätter name med namnet du skickar och lagrar en matris av type du skickar (name finns där så att du kan sammanfoga det till basstrukturen och fortfarande har en giltig identifierare – darray_int * är inte ett giltigt namn för en struktur), medan DARRAY_IMPL-makrot definierar de funktioner som fungerar på den strukturen (i så fall markeras de statiska bara så att man ring bara definitionen en gång och inte separera allt).

Detta skulle användas 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

Jag tror att mallar används mycket som ett sätt att återanvända containertyper som har en många algoritmiska värden som dynamiska matriser (vektorer), kartor, träd, etc. sortering etc.

Utan mallar, nödvändigtvis, innehåller dessa implementeringar på ett generiskt sätt och ges bara tillräckligt med information om vilken typ som krävs för deras domän. Till exempel, med en vektor behöver de bara datan för att kunna vara kapabel och de behöver veta storleken på varje objekt.

Låt oss säga att du har en behållarklass som heter Vector som gör detta. Det tar ogiltighet *. Den enkla användningen av detta skulle vara att applikationslagerkoden gör mycket gjutning. Så om de hanterar kattobjekt måste de kasta Cat * till ogiltiga * och tillbaka överallt. Att kasta applikationskod med cast har uppenbara problem.

Mallar löser detta.

Ett annat sätt att lösa det är att skapa en anpassad behållartyp för den typ du lagrar i behållaren. Så om du har en Cat-klass skulle du skapa en CatList-klass härledd från Vector.Du överbelastar sedan de få metoderna du använder och introducerar versioner som tar Cat-objekt istället för ogiltiga *. Så du skulle överbelasta metoden Vector :: Add (void *) med Cat :: Add (Cat *), som internt helt enkelt skickar parametern till Vector :: Add (). Sedan i din applikationskod skulle du ringa överbelastad version av Lägg till när du skickar in ett kattobjekt och undvik sålunda gjutning. För att vara rättvis skulle Add-metoden inte kräva en cast eftersom ett Cat * -objekt omvandlas till ogiltigt * utan en cast. Men metoden för att hämta ett objekt, såsom indexöverbelastning eller en Get () -metod skulle.

Det andra tillvägagångssättet, det enda exemplet på vilket jag minns från början av 90-talet med ett stort applikationsramverk, är att använda ett anpassat verktyg som skapar dessa typer över klasser. Jag tror att MFC gjorde det. De hade olika klasser för behållare som CStringArray, CRectArray, CDoulbeArray, CIntArray osv. I stället för att upprätthålla dubblettkod gjorde de någon typ av metaprogrammering som liknar makron med hjälp av ett externt verktyg som skulle generera klasserna. De gjorde verktyget tillgängligt med Visual C ++ om någon ville använda det – det gjorde jag aldrig. Kanske borde jag ha gjort det. Men vid den tidpunkten pratade experterna om ”Sane subset of C ++” och ”You don t need Templates”

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *