To pytanie ma już tutaj odpowiedzi :
Komentarze
Odpowiedź
Oprócz void *
wskaźnika, który jest zakryty w odpowiedzi Roberta , użyto podobnej techniki (Zastrzeżenie: 20-letnia pamięć):
#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; }
Gdzie zapomniałem dokładnej magii preprocesora wewnątrz collection.h
, ale tak było coś takiego:
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
Komentarze
Odpowiedź
tradycyjnym sposobem implementacji typów ogólnych bez posiadania typów ogólnych (powód, dla którego zostały utworzone szablony) jest użycie wskaźnika void.
typedef struct Item{ void* data; } Item; typedef struct Node{ Item Item; struct Node* next; struct Node* previous; } Node;
W tym przykładowym kodzie drzewo binarne lub Lista podwójnie połączona może być reprezentowana. Ponieważ item
zawiera pusty wskaźnik, można tam przechowywać dowolny typ danych. Oczywiście musisz znać typ danych w czasie wykonywania, aby móc przesłać je z powrotem do użytecznego obiektu.
Komentarze
Odpowiedź
Jak wskazywały inne odpowiedzi, możesz użyć void*
dla ogólnych struktur danych.W przypadku innych rodzajów polimorfizmu parametrycznego makra preprocesora były używane, jeśli coś było powtarzane dużo (np. Dziesiątki razy). Jednak szczerze mówiąc, przez większość czasu przy umiarkowanych powtórzeniach ludzie po prostu kopiowali i wklejali, a następnie zmieniali typy, ponieważ jest wiele pułapek związanych z makrami, które sprawiają, że są problematyczne.
Naprawdę potrzebujemy nazwa odwrotności paradoksu blubów , w którym ludzie mają trudności z wyobrazeniem sobie programowania w mniej wyrazistym języku, ponieważ często pojawia się to na tej stronie. Jeśli nigdy nie używałeś języka z ekspresyjnymi sposobami implementacji polimorfizmu parametrycznego, tak naprawdę nie wiesz, czego ci brakuje. Po prostu akceptujesz kopiowanie i wklejanie jako nieco denerwujące, ale konieczne.
W wybranych przez Ciebie językach występują nieefektywności, o których nawet nie jesteś świadomy. Za dwadzieścia lat ludzie będą się zastanawiać, jak ich wyeliminowałeś. Krótka odpowiedź brzmi: nie, ponieważ nie wiedziałeś, że możesz.
Komentarze
Odpowiedź
Pamiętam, kiedy gcc był dostarczany z genclass
– programem, który jako dane wejściowe przyjmował zestaw typów parametrów ( np. klucz i wartość dla mapy) oraz specjalny plik składni, który opisuje sparametryzowany typ (powiedzmy Mapę lub Wektor) i generuje prawidłowe implementacje C ++ z wypełnionymi typami parametrów.
Więc jeśli potrzebne Map<int, string>
i Map<string, string>
(pamiętaj, że to nie była rzeczywista składnia) uruchom ten program dwukrotnie, aby wygenerować coś w rodzaju map_string_string.h i map_int_string.h, a następnie użyj ich w swoim kodzie.
Zobacz stronę podręcznika dla genclass
i dokumentacja z biblioteki GNU C ++ 2.0 , aby uzyskać więcej szczegółów.
Odpowiedz
[Do OP: Nie próbuję cię osobiście czepiać, ale zwiększam twoją i innych „świadomość myślenia o logice pytania ( s) zapytał na SE i gdzie indziej. Proszę nie brać tego do siebie!]
Tytuł pytania jest dobry, ale poważnie ograniczasz zakres swoich odpowiedzi, włączając „… sytuacje, w których potrzebowali wygenerowania kodu w czasie kompilacji. „Na tej stronie znajduje się wiele dobrych odpowiedzi na pytanie, jak generować kod w czasie kompilacji w C ++ bez szablonów, ale aby odpowiedzieć na pytanie, które zadałeś pierwotnie:
Co robili ludzie przed szablonami w C ++?
Odpowiedź brzmi oczywiście, że oni (my) ich nie używali. Tak, żartuję, ale szczegóły pytania w treści wydają się (być może przesadzone) zakładają, że każdy kocha szablony i że bez nich nie byłoby żadnego kodowania.
Na przykład ukończyłem wiele projektów kodowania w różnych językach bez konieczności generowania kodu w czasie kompilacji i wierzę, że inni też. Jasne, problem rozwiązany przez szablony był na tyle swędzący, że ktoś go podrapał, ale scenariusz postawiony w tym pytaniu w dużej mierze nie istniał.
Rozważ podobne pytanie w samochodach:
W jaki sposób kierowca zmieniał bieg na inny za pomocą zautomatyzowanej metody, która zmieniała biegi za Ciebie, zanim wynaleziono automatyczną skrzynię biegów?
Pytanie jest oczywiście głupie. Pytanie, w jaki sposób dana osoba zrobiła X przed wynalezieniem X, nie jest tak naprawdę ważnym pytaniem. Odpowiedź brzmi ogólnie: „nie zrobiliśmy tego i nie przegapiliśmy tego, ponieważ nie wiedzieliśmy, że kiedykolwiek będzie istnieć”.Tak, łatwo jest dostrzec korzyści po fakcie, ale założenie, że wszyscy stali w pobliżu, kopiąc pięty, czekając na automatyczną transmisję lub szablony w C ++, naprawdę nie jest prawdą.
Na pytanie „w jaki sposób kierowcy zmieniali biegi, zanim wynaleziono automatyczną skrzynię biegów?” Można rozsądnie odpowiedzieć „ręcznie” i to jest typ odpowiedzi, które tu otrzymujesz. Może to być nawet rodzaj pytania, które chciałeś zadać.
Ale to nie było to, które zadałeś.
Więc:
P: Jak ludzie używali szablonów przed wynalezieniem szablonów?
O: Nie.
P: W jaki sposób ludzie używali szablonów przed wynalezieniem szablonów, kiedy musieli używać szablonów ?
O: Nie musieliśmy ich używać. Dlaczego założyliśmy, że to zrobiliśmy? (Dlaczego zakładamy, że to robimy?)
P: Jakie są alternatywne sposoby osiągnięcia wyników, które zapewniają szablony?
O: Istnieje wiele dobrych odpowiedzi powyżej.
Proszę, zastanów się nad logicznymi błędami w swoich postach przed wysłaniem.
[Dziękuję! Proszę, nie ma w tym nic złego.]
Komentarze
Odpowiedź
Odpowiedź
Jak już powiedział Robert Harvey, wskaźnik void jest ogólnym typem danych.
Przykład ze standardowej biblioteki C, jak posortować tablicę double z sortowaniem ogólnym:
double *array = ...; int size = ...; qsort (array, size, sizeof (double), compare_doubles);
Gdzie compare_double
jest zdefiniowane jako:
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); }
Podpis qsort
jest zdefiniowany w stdlib.h:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *) );
Pamiętaj, że nie ma typu sprawdzanie w czasie kompilacji, nawet w czasie wykonywania. Jeśli posortujesz listę ciągów za pomocą powyższego komparatora, który oczekuje podwojeń, z przyjemnością spróbuje zinterpretować binarną reprezentację ciągu jako podwójnego i odpowiednio posortować.
Odpowiedź
Można to zrobić w następujący sposób:
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; \ }
Makro DARRAY_TYPEDECL skutecznie tworzy definicję struktury (w jednym wierszu), zastępując name
z podaną nazwą i przechowywanie tablicy przekazanych type
(name
jest tam, aby można było je połączyć do nazwy struktury bazowej i nadal mają poprawny identyfikator – darray_int * nie jest poprawną nazwą dla struktury), podczas gdy makro DARRAY_IMPL definiuje funkcje, które działają na tej strukturze (w takim przypadku są one oznaczone jako statyczne tylko po to, aby można było wywołuj definicję tylko raz i nie oddzielaj wszystkiego).
Byłoby to użyte jako:
#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();
Odpowiedź
Myślę, że szablony są często używane jako sposób ponownego wykorzystania typów kontenerów, które mają wiele wartości algorytmicznych, takich jak tablice dynamiczne (wektory), mapy, drzewa itp., sortowanie itp.
Bez szablonów, koniecznie, zawierają one implementacje, które są napisane w sposób ogólny i zawierają wystarczającą ilość informacji o typie wymaganym dla ich domeny. Na przykład w przypadku wektora potrzebują one tylko danych, aby być w stanie „blt” i muszą znać rozmiar każdego elementu.
Powiedzmy, że masz klasę kontenera o nazwie Vector, która to robi. Zajmuje pustkę *. Uproszczone użycie tego polegałoby na tym, że kod warstwy aplikacji wykonywałby dużo rzutowania. Więc jeśli zarządzają obiektami Cat, muszą rzucić Cat * to void * iz powrotem w dowolne miejsce. Zaśmiecanie kodu aplikacji rzutami ma oczywiste problemy.
Szablony rozwiązują ten problem.
Innym sposobem rozwiązania tego problemu jest utworzenie niestandardowego typu kontenera dla typu, który ponownie przechowujesz w kontenerze. Więc jeśli masz klasę Cat, utworzysz klasę CatList pochodzącą z klasy Vector.Następnie przeciążasz kilka metod, których używasz, wprowadzając wersje, które przyjmują obiekty Cat zamiast void *. Więc „d przeciążasz metodę Vector :: Add (void *) za pomocą Cat :: Add (Cat *), która wewnętrznie po prostu przekazuje parametr do Vector :: Add (). Następnie w kodzie aplikacji należy wywołać metodę przeciążona wersja Add podczas przekazywania obiektu Cat, co pozwala uniknąć rzutowania. Aby być uczciwym, metoda Add nie wymagałaby rzutowania, ponieważ obiekt Cat * jest konwertowany na void * bez rzutowania. Jednak metoda pobierania elementu, taka jak przeciążenie indeksu lub metoda Get (), wymagałaby tego.
Innym podejściem, którego jedynym przykładem, który pamiętam z wczesnych lat 90-tych z dużym frameworkiem aplikacji, jest użycie niestandardowego narzędzia, które tworzy te typy w klasach. Myślę, że to zrobiło MFC. Miały różne klasy dla kontenerów jak CStringArray, CRectArray, CDoulbeArray, CIntArray itp. Zamiast utrzymywać zduplikowany kod, wykonali pewnego rodzaju metaprogramowanie podobne do makr, używając zewnętrznego narzędzia, które generowałoby klasy. Udostępnili to narzędzie w Visual C ++ na wypadek, gdyby ktoś chciałem go użyć – nigdy tego nie robiłem. Może powinienem. Ale wtedy eksperci reklamowali „Rozsądny podzbiór C ++” i „Nie potrzebujesz szablonów”