Template Methode nur spezialisiert vererben
-
Tut mir Leid, das war zu abstrakt für mich. Kannst du konkret erklären, warum folgendes nicht möglich sein soll?
Zustand 1:
class Base { public: template< class Type > virtual void func( Type ) { } }; class A : public Base { public: void func( int ) { // überschreibt void Base::func<int>(int) } }; class B : public Base { public: void func( double ) { // überschreibt void Base::func<double>(double) } };Es ist offensichtlich wie das übersetzt werden könnte: Es gibt 2 virtuelle Funktionen in Base, die zwar jeweils zum gleichen Template gehören, aber sich ansonsten ja verhalten, als gäbe es 2 Funktionen func_int und func_double.
Wenn jetzt ein User daherkommt und sowas macht wieclass C : public Base { public: void func( short ) { } };bedeutet das, dass es jetzt 3 Funktionen gibt, nämlich func_int, func_double und func_short. Wenn es nicht möglich ist, unabhängig für func_short einen eigenen vtable zu erstellen, dann können immernoch Base, A und B mit C zusammen neu übersetzt werden. Mit pure virtual functions, da gebe ich euch ja Recht, wäre das ganze etwas komplizierter. Ich bin mir zwar ziemlich sicher, dass es möglich ist, auch so etwas sinnvoll zu implementieren, aber jetzt ist es mir zu spät für solche Gedankenspiele. Notfalls könnte man immernoch allein den Gebrauch von pure virtual template functions verbieten.
-
GorbGorb schrieb:
Mit pure virtual functions, da gebe ich euch ja Recht, wäre das ganze etwas komplizierter.
Nee, einfacher.
class Base { public: template< class Type > virtual void func( Type ) { cout<< boost::something_happens<T>::value; } };ist schwieriger, ja unmöglicg, ohne den ganzen Quellcode den Erben zu geben als
class Base { public: template< class Type > virtual void func( Type ) = 0; };Ich denke da zum Beispiel an
class A: public Base { template<> /*virtual*/ void func<double>(double d) { cout<<"ima double\n"; Base::func(d); } };Andererseits, warum nicht den ganzen Quellcode den Erben geben? Tut man bei Templates ja eh.
-
@Volkard: Diese Funktion müsste allerdings alle Konkretisierungen kennen, um den Dispatch durchführen zu können.
@GorbGorb: Mal abgesehen davon, dass man, selbst wenn man den gesamten Code hat, das gesamte Kompilationsmodell von C++ über den Haufen werfen müsste, um es so zu machen, wie du es erwartest, ist es stumpf nicht so, dass man zur Compilezeit den gesamten Code, den man benutzt, kennt - wir haben alle schon mit Bibliotheken von Dritten gearbeitet. Wie soll der Compiler (bzw. Linker) aus einer kompilierten DLL die virtuelle Funktionstabelle einer Klasse
namespace { // <-- das bedeutet, die Klasse ist nach außen nicht sichtbar. class Foo : public Base { // hier vielleicht nicht mal eine Überladung, sondern nur ein paar // zusätzliche Funktionen }; }herausfischen und anpassen, wenn das Hauptprogramm eine neue Konkretisierung hinzufügt? Den eigentlichen Code hat man ja dann nicht mehr.
Dazu: Selbst wenn man es im Compiler lösen könnte, wäre das nicht genug - der Laufzeitlinker ist gefragt. Wenn ich aus einer Bibliothek eine FUnktion
Base *make_foo();exportiere, die per dlsym oder GetProcAddress zur Laufzeit dynamisch gelinkt werden soll und Objekte einer nur der DLL in allen Details bekannten, von Base abgeleiteten Klasse zurückgibt, in Abwesenheit eines Compilers die gesamte Hierarchie upgedatet werden? Und sie müsste upgedatet werden. Der Standard kann Callback-Mechanismen nicht implizit verbieten.
Was, wenn man mehr als eine Bibliothek linken will? Dann müssen nicht nur die virtuellen Funktionstabellen angepasst werden, sondern auch alle Stellen im Code, die sie benutzen. Wenn Bibliothek 1 eine Funktion
void do_something_with(Base &);exportiert und Bibliothek 2 dafür sorgt, dass die Reihenfolge der Funktionen in den virtuellen Funktionstabellen sich ändert, ruft die Funktion auf einmal die falsche Methode auf. Wenn Bibliothek 1 bevorzugte Behandlung anmeldet und nur die Überladungen aus Bibliothek 2 verschoben werden, hast du das selbe Problem mit entsprechenden Funktionen in Bibliothek 2.
Auch hier muss man darauf vorbereitet sein, dass eine Bibliothek zur Laufzeit dynamisch gelinkt wird. Der Compiler soll sich damit befassen? Ein Compiler ist nirgends in Sicht.
Ich glaube nicht, dass es auf der Welt einen Laufzeitlinker gibt, der solche Mechanismen beherrscht. Ich glaube auch nicht, dass es jemals einen geben wird.
-
Nexus schrieb:
Skym0sh0, was willst du genau erreichen (nicht wie)?
ich habe eine basis klasse die eine init methode(wehe es kommt mir einer mit RAII, da kann ich aus anderen gründen nicht nutzen - die objekte sind alle erstellt, werden aber nur amnchmal benötigt) anbietet und die einen parameter erwartet (besser sage ich erstmal, erwarten könnte - bin mir nicht sicher ob ich das so in der art lösen soll oder besser ganz anders). jetzt braucht aber deriveA einen String als parameter während DeriveB einen vector<float> braucht und deriveC braucht FooBar... usw.
das ding ist, ich greife über zeiger auf die basisklasse auf die objekte zu. wo ich die init methode aufrufe lässt sich eine fallunterscheidung einbauen (wobei ich merke grad das das alles nicht so toll wäre und die polymorphie aushebeln würde...)
ich muss dazu sagen, mir kam diese idee mit den virtuellen template methoden nur mal kurz am rande ohne weiter drüber nachgedacht zu haben, ich hab also kein konzept die dann auch so schon zu benutzen. mein ziel bei diesem lernprojekt ist, sauberen code zu schreiben (ich führe mir deshalb auch derzeitig einschlägige scott meyers lektüre zu gemüte)
-
@seldon: Hast mich überzeugt, wieder was gelernt, danke

@Skym0sh0: Hab ich das jetzt richtig verstanden, dass du sowas willst wie
class Container { Base *ptr; //hier steckt irgendein Derived drin template< class Type > void init( Type Object ) { ptr->init( Object ); //Wenn Derived init(Type) überschrieben hat, dann soll das aufgerufen //werden, ansonsten soll nichts passieren. } };
-
GorbGorb schrieb:
@Skym0sh0: Hab ich das jetzt richtig verstanden, dass du sowas willst wie
class Container { Base *ptr; //hier steckt irgendein Derived drin template< class Type > void init( Type Object ) { ptr->init( Object ); //Wenn Derived init(Type) überschrieben hat, dann soll das aufgerufen //werden, ansonsten soll nichts passieren. } };nein, sowas habe ich:
class Base { public: template<typename T> virtual void init(T const& x) = 0; };class DeriveA : public Base { public: template<> virtual void init(String const& x) // braucht einen string für seine initialisierung { // do something } } class DeriveB : public Base { public: template<> virtual void init(std::vector<int> const& x) // braucht einen vector für seine initialisierung { // do something } } class DeriveC : public Base { public: template<> virtual void init(std::complex<double> const& x) // braucht eine complexe zahl für seine initialisierung { // do something } }class Manager { std::vector<Base*> vec; // wird auch irgendwo gefüllt mit den klassen... public: void init(int index) { if ( index == someThingSpecial1 ) vec[index]->init("String1"); if ( index == someThingOtherSpecial ) vec[index]->init(std::vector<int>(100, 5); if ( index == lolFooBar ) vec[index]->init(std::complex<double>(5.0, 3.0); } };das ist nur schematisch gemeint, weder getestet noch kompilierbar und auch von der genauen implementierung meines projektes abweichend
-
Wenn du ohnehin eine Fallunterscheidung hast, solltest du statische Typinformation über die abgeleiteten Objekte besitzen. Dann kannst du auch gleich die richtige
Init()-Methode aufrufen...Aber grundsätzlich sind solche Fallunterscheidungen fast immer ein Hinweis auf schlechtes Design. Was sind das semantisch für Klassen, was genau tut
Init()?
-
deshalb hab ich irgendwo auch gesagt, dass mir auffiel, dass es doch nicht so toll ist...
init initialisiert das gewählte objekt, aber das braucht halt mal nen string, mal en array, mal sonstwas
eine möglichkeit wäre als parameter einen zeiger auf eine andere klasse (oder strukt) mitzugeben und von eben dieser klasse(oder struct) spezialierungen zu erben um deren typen sich die objekte dann intern selbst kümmern können
also so:
struct BaseInfos { }; class Base { public: virtual void init(BaseInfos * b) = 0; };struct DeriveAInfos : public BaseInfos { std::string s; int foo; }; class DeriveA { public: virtual void init(BaseInfos * b) { DeriveAInfos * d = dynamic_cast<DeriveAInfos*>(b); doWhatever(d->s, d->foo); } };so wäre es denke ich was besser alles, oder?!
-
Sowas wie
class Manager { std::vector<Base*> vec; // wird auch irgendwo gefüllt mit den klassen... public: void init(int index) { if ( index == someThingSpecial1 ) reinterpret_cast<DeriveA*>(vec[index])->init("String1"); if ( index == someThingOtherSpecial ) reinterpret_cast<DeriveB*>(vec[index])->init(std::vector<int>(100, 5); if ( index == lolFooBar ) reinterpret_cast<DeriveC*>(vec[index])->init(std::complex<double>(5.0, 3.0); } };wäre nicht möglich? Du scheinst ja durch den Index schon zu wissen, was für ein spezieller Typ es ist, sonst könntest du ja gar nicht wissen, dass vec[index] ein Typ drin steckt, der diesen speziellen "Konstruktor" implementiert hat. Da kannst du auch genauso gut einfach eine ganz normale Memberfunktion aufrufen. Ich würde mich hier aber Nexus anschließen, das sieht mir nicht unbedingt nach gutem design aus.
-
Dynamischer Downcast ist selten besser

Das Problem beginnt bereits früher. Wenn du weisst, dass du einen
std::vectoranInit()übergeben musst, kennst du ja auch das abgeleitete Objekt – schliesslich muss dieses mit demstd::vectoretwas anfangen können. Das wiederum bedeutet, dass du nicht über die Basisklasse arbeiten musst, sondern direktDerived::Init()aufrufen kannst.Erklär mal, wieso das nicht geht, du aber dennoch Fälle unterscheidest (durch die verschiedenen übergebenen Argumente wie
std::vector,std::stringetc.). Und dazu wäre es nützlich, die eigentliche Aufgabe der Klasse zu kennen, also nicht nurBaseundDerived.
-
GorbGorb schrieb:
reinterpret_castDas wird richtig laut knallen. Nimm
static_castwenn du dir über die Vererbungsbeziehung sicher bist, unddynamic_castwenn nicht. Aber ein komplett anderer Ansatz ohne Downcast wäre nochmals viel besser...
-
wartet mal kurz, ejtzt wirds langsam was komplex.
downcast ist wenn ich die derive klasse nach base caste, oder?!? (und das geht implizit)
upcast ist, wenn man aus base ein derive holt (explizit, sprich dynamic/static cast nötig?!?)
ist denn die methode ein parameterstruct per base und derive struct zu übergeben ok?
-
Skym0sh0 schrieb:
downcast ist wenn ich die derive klasse nach base caste, oder?!? (und das geht implizit)
upcast ist, wenn man aus base ein derive holt (explizit, sprich dynamic/static cast nötig?!?)
Nein, andersrum! Denk Dir einen Vererbungsbaum. Da sind die Basisklassen natürlich oben.
(Informatiker haben doch den Keller im Speicher und malen Bäume mit der Wurzel oben.)
-
achso
ja logisch eigentlich 
danke
-
Es macht doch keinen Sinn, dass die Basisklasse eine init-Methode hat, die verschiedene Parametertypen erwartet. Dann kann der Aufruf sowieso nicht polymorph sein -> wozu in der Basisklasse halten?
Und was erzeugt init? Also wo liegen die Objekte nachher? Werden die zurückgegeben? Oder affektiert das die Klasseninstanz selbst? Und kann ich die Klasse nutzen, bevor ich init aufgerufen habe? Falls ja, was ändert init dann? Und wofür ist die Vererbungsbeziehung in erster Linie da? Oder willst Du damit ebenfalls das Initialisierungsproblem lösen?
Für mich klingt das nach dem init ein wenig nach falschen Verantwortlichkeiten. Entweder, wir können die Klasse sofort nach Instanziierung nutzen oder wir kriegen irgend ein tolles Addon, das wir aber zur Laufzeit dazubsteln wollen. Wenn Du letzteres willst, würde ich das in ein anderes Objekt verlagern und z.B. über Dependency Injection einfügen: (das Wort ist ja gerade "in", aber es wird wohl sehr bald nervig sein so ein Standardkonzept ständig mit nem Buzzword zu versehen :p)
class Class1 { private: Base* base; public: void setWhatever(Base* base); }; class Base { // ... }; class Derived1 : public Base { public: Derived1(const std::string&); }; class Derived2 : public Base { public: Derived2(int); };