Lokale Kopie von Base Klasse anlegen?
-
volkard schrieb:
abgesehen von den mehr oder weniger pfiffigen vorschlägen:
dein vorhaben ist doch unfug.Warum ist es Unfug eine lokale Kopie einer Base Klasse anfertigen zu wollen?
Jockelx schrieb:
Mach doch sowas (grob, smartPointer & Co fehlen)
Also ich hab das mal gemacht und hab dazu noch eine Frage. Das war ja der Code mit rohen Pointern:
struct Base { virtual int output(int input) = 0; virtual Base* clone() const = 0; }; struct Derived1 : Base { Derived1() : prev_input(0) {} int prev_input; int output(int input) { prev_input += input; return prev_input; } Derived1* clone() const { return new Derived1(*this); } }; void foo(Base &bar) { Base *clone = bar.clone(); }Was auch klappt. Jetzt habe ich zu Smart-Pointern gewechselt und habe das hier:
struct Base { virtual int output(int input) = 0; virtual std::shared_ptr<Base> clone() const = 0; }; struct Derived1 : Base { Derived1() : prev_input(0) {} int prev_input; int output(int input) { prev_input += input; return prev_input; } std::shared_ptr<Base> clone() const // <- Frage { return std::make_shared<Derived1>(*this); } }; void foo(Base &bar) { auto clone = bar.clone(); }Dabei geht das aber nur wenn ich einen
std::shared_ptr<Base>zurückgebe und nicht bei einemstd::shared_pointer<Derived1>da smart pointer anscheinend keine kovarianten Rückgabewerte unterstützen.Allerdings klappt es trotzdem so wie ich will, daher mal die Frage: Bringt das irgendwelche Probleme mit sich, das so zu machen?
-
happystudent schrieb:
volkard schrieb:
abgesehen von den mehr oder weniger pfiffigen vorschlägen:
dein vorhaben ist doch unfug.Warum ist es Unfug eine lokale Kopie einer Base Klasse anfertigen zu wollen?
Um aus einem Hund eine Katze zu machen, indem man den gemeinsamen Säugetier-Kram rauskopiert und ganz vorne Schnurrhaare dranklebt? Das ist so abwegig, daß ich gar nicht verstehe, daß Du so viele Antworten bekommen hast.
Mach mal ein Beispiel nicht nur mit foo und bar und ganz abstrakten Templates, wo dein Vorhaben sinnvoll ist und nicht geradewegs auf Abwege und umkippende Software führt.
Ich muss mir jetzt nochmal http://www.c-plusplus.net/forum/75672-full durchlesen, damit Du mich nicht verwirrst.
-
volkard schrieb:
Um aus einem Hund eine Katze zu machen, indem man den gemeinsamen Säugetier-Kram rauskopiert und ganz vorne Schnurrhaare dranklebt? Das ist so abwegig, daß ich gar nicht verstehe, daß Du so viele Antworten bekommen hast.
Mach mal ein Beispiel nicht nur mit foo und bar und ganz abstrakten Templates, wo dein Vorhaben sinnvoll ist und nicht geradewegs auf Abwege und umkippende Software führt.
Ich muss mir jetzt nochmal http://www.c-plusplus.net/forum/75672-full durchlesen, damit Du mich nicht verwirrst.
Ok, hier ein weniger abstraktes Beispiel:
struct Bauelement { virtual int output(int input) = 0; virtual std::shared_ptr<Bauelement> clone() const = 0; }; struct Heizplatte : public Bauelement { Heizplatte() : prev_strom(0) {} int prev_strom; int output(int strom) { prev_strom += strom; // Stark vereinfacht return prev_strom; } std::shared_ptr<Bauelement> clone() const { return std::make_shared<Heizplatte>(*this); } }; void vergleiche_zwei_bauelemente(Bauelement &b1_, Bauelement &b2_) { auto b1 = b1_.clone(), b2 = b2_.clone(); // Hier müssen wieder outputs berechnet werden, die Bauelemente werden anhand ihres outputs verglichen // Dank der clone-Funktion bleiben die ursprünglichen Bauelemente unverändert, was auch so sein soll b1.get()->output(1); b2.get()->output(5); // Vergleiche die outputs } int main() { Heizplatte h1; Heizplatte h2; // Stelle Berechnungen an, die den Speicher der Bauelemente "aufladen" h1.output(1); h2.output(3); // Vergleiche die Bauelemente; durch den Vergleich sollen diese aber _nicht_ verändert werden vergleiche_zwei_bauelemente(h1, h2); }Deinen verlinkten Artikel habe ich gelesen, aber die dort beschriebene Problematik passt nicht auf mein Problem: Heizplatte ist ein Bauelement und besitzt keines. Es kann zwar auch (beliebig viele) besitzen, von außen betrachtet verhält es sich aber wie jedes andere Bauelement auch.
-
happystudent schrieb:
volkard schrieb:
Um aus einem Hund eine Katze zu machen, indem man den gemeinsamen Säugetier-Kram rauskopiert und ganz vorne Schnurrhaare dranklebt? Das ist so abwegig, daß ich gar nicht verstehe, daß Du so viele Antworten bekommen hast.
Mach mal ein Beispiel nicht nur mit foo und bar und ganz abstrakten Templates, wo dein Vorhaben sinnvoll ist und nicht geradewegs auf Abwege und umkippende Software führt.
Ich muss mir jetzt nochmal http://www.c-plusplus.net/forum/75672-full durchlesen, damit Du mich nicht verwirrst.
Ok, hier ein weniger abstraktes Beispiel:
struct Bauelement { virtual int output(int input) = 0; virtual std::shared_ptr<Bauelement> clone() const = 0; }; struct Heizplatte : public Bauelement { Heizplatte() : prev_strom(0) {} int prev_strom; int output(int strom) { prev_strom += strom; // Stark vereinfacht return prev_strom; } std::shared_ptr<Bauelement> clone() const { return std::make_shared<Heizplatte>(*this); } }; void vergleiche_zwei_bauelemente(Bauelement &b1_, Bauelement &b2_) { auto b1 = b1_.clone(), b2 = b2_.clone(); // Hier müssen wieder outputs berechnet werden, die Bauelemente werden anhand ihres outputs verglichen // Dank der clone-Funktion bleiben die ursprünglichen Bauelemente unverändert, was auch so sein soll b1.get()->output(1); b2.get()->output(5); // Vergleiche die outputs } int main() { Heizplatte h1; Heizplatte h2; // Stelle Berechnungen an, die den Speicher der Bauelemente "aufladen" h1.output(1); h2.output(3); // Vergleiche die Bauelemente; durch den Vergleich sollen diese aber _nicht_ verändert werden vergleiche_zwei_bauelemente(h1, h2); }Deinen verlinkten Artikel habe ich gelesen, aber die dort beschriebene Problematik passt nicht auf mein Problem: Heizplatte ist ein Bauelement und besitzt keines. Es kann zwar auch (beliebig viele) besitzen, von außen betrachtet verhält es sich aber wie jedes andere Bauelement auch.
Dann stimmt doch das Vergleichen nicht oder fehlt.
Bauelement kopieren, obwohl sie leer ist? Kopie einer abstrakten Basisklasse?
Also es geht nur drum, zwei Bauelemente vergleichen zu können, indem Du ihnen einen Eingangssignalverlauf schickst und sie gleich drauf reagieren sollen? Das wäre dann doch die Schnittstelle?
Mir scheint, Du trennt nicht zwischen Bauelemetschablone(Klasse) und Bauelement(Instanz). "aufladen" wäre dann Instanziieren. Zwei Objekte sind gleich, wenn sie von der selben Klasse sind und ihre Attribute gleich sind, reicht das nicht? An den Speicher des Verzögerers, hier nur "int prev_strom;" willste ran? Ist aber nicht "lokale Kopie von Base Klasse anlegen".
Sollte das Bauelement Bauelementspeicher haben, den man einfrieren und rumvertauschen kann?
-
volkard schrieb:
Dann stimmt doch das Vergleichen nicht oder fehlt.
Ja, das ist so nicht vollständig, sollte nur der Verdeutlichung dienen.
volkard schrieb:
Bauelement kopieren, obwohl sie leer ist? Kopie einer abstrakten Basisklasse?
Wieso leer? Die sind nicht leer, bevor ich die vergleichs-Funktion aufrufe habe ich doch bereits geheizt (mittels der output Funktion).
volkard schrieb:
Also es geht nur drum, zwei Bauelemente vergleichen zu können, indem Du ihnen einen Eingangssignalverlauf schickst und sie gleich drauf reagieren sollen? Das wäre dann doch die Schnittstelle?
Mir scheint, Du trennt nicht zwischen Bauelemetschablone(Klasse) und Bauelement(Instanz). "aufladen" wäre dann Instanziieren.Nein, aufgeladen bzw. entladen werden die Bauelemente durch Benutzung. Es geht letztendlich um die Vorhersage von Lebensdauer der Bauelemente, daher sieht das Ganze ungefähr so aus:
while (true) { h1.output(1); h2.output(3); // Nach dem Vergleich sollen die Bauelemente im gleichen Zustand sein wie davor vergleiche_zwei_bauelemente(h1, h2); if (irgendeine_bedingung) break; }volkard schrieb:
Zwei Objekte sind gleich, wenn sie von der selben Klasse sind und ihre Attribute gleich sind, reicht das nicht?
Es geht nicht darum ob zwei Bauelemente gleich sind, sondern ob ihr Verhalten gleich ist. Zwei Heizplatten etwa können völlig unterschiedlich gebaut sein und auch völlig unterschiedliche Attribute haben und trotzdem gleiches Input/Output Verhalten aufweisen (und umgekehrt).
volkard schrieb:
An den Speicher des Verzögerers, hier nur "int prev_strom;" willste ran? Ist aber nicht "lokale Kopie von Base Klasse anlegen".
Sollte das Bauelement Bauelementspeicher haben, den man einfrieren und rumvertauschen kann?Ne, an den will ich eben nicht ran. Ich hab irgendein Bauelement, von dem ich nur weiß dass ich ihm einen Strom geben kann und es mir einen Output zurückgibt. Für dieses Bauelement will ich jetzt berechnungen anstellen die seinen internen State nur lokal verändern, nämlich nur für diese lokalen Berechnungen (hier in vergleiche_zwei_bauelemente). Danach will ich weitere Berechnungen mit dem gleichen Bauelement und dessen ursprünglichem Status anstellen können.
-
happystudent schrieb:
Was auch klappt. Jetzt habe ich zu Smart-Pointern gewechselt und habe das hier:
(...)
Dabei geht das aber nur wenn ich einenstd::shared_ptr<Base>zurückgebe und nicht bei einemstd::shared_pointer<Derived1>da smart pointer anscheinend keine kovarianten Rückgabewerte unterstützen.Richtig.
Auch dabei hilft CRTP (+ non-virtual-interface):
EDIT: blödsinnige Implementierung korrigiert (siehe Kommentare in clone())class Thing { public: std::shared_ptr<Thing> clone() const { return clone_impl(); } private: virtual std::shared_ptr<Thing> clone_impl() const = 0; }; template <class Derived, class Base> class ThingBase : public Base { public: // Wir überdecken die clone() Funktion der Basisklasse, weil's praktisch ist wenn wir std::shared_ptr<Derived> zurückgeben können std::shared_ptr<Derived> clone() const { // Wir müssen hier aber auf jeden Fall auch clone_impl aufrufen, // damit Aufrufer die z.B. Derived::clone() auf ein MoreDerived Objekt // aufrufen auch wirklich einen MoreDerived Klon bekommen! std::shared_ptr<Thing> const ptr = clone_impl(); if (!ptr) // Sollte bei einer clone() Funktion nicht passieren, aber wir nur um zu zeigen wie man es z.B. bei einer "try_create" Funktion machen würde... return std::shared_ptr<Derived>(); std::shared_ptr<Derived> const derivedPtr = std::dynamic_pointer_cast<Derived>(ptr); if (!derivedPtr) // Ist ein WTF wenn clone_impl() keinen passenden Typ zurückgibt, aber wir sollten es trotzdem checken throw std::bad_cast(); return derivedPtr; } private: // Wir überschreiben die clone_impl() Funktion der Basisklasse, damit clone() der Basisklasse korrekt funktioniert virtual std::shared_ptr<Thing> clone_impl() const { std::shared_ptr<Thing> ptr(new Derived(*static_cast<Derived const*>(this))); return ptr; } }; // Abgeleitete Klassen class DerivedThing : public ThingBase<DerivedThing, Thing> { }; class MoreDerivedThing : public ThingBase<MoreDerivedThing, DerivedThing> { }; class AnotherDerivedThing : public ThingBase<AnotherDerivedThing, Thing> { }; // ...Bzw. mit nem Makro kannst du es natürlich genau so machen.
Der Vorteil der Makro-Variante ist z.B. dass es dir die Klassendiagramme in automatisch generierten Dokumentationen (Doxygen, ...) nicht mit lauter Zwischenklassen vollsaut.happystudent schrieb:
Allerdings klappt es trotzdem so wie ich will, daher mal die Frage: Bringt das irgendwelche Probleme mit sich, das so zu machen?
Ich sehe kein Problem.
-
hustbaer schrieb:
Richtig.
Auch dabei hilft CRTP (+ non-virtual-interface):
Das non-virtual-interface sieht interessant aus, kannte ich noch nicht... Btw, gibts eigentlich einen Grund dafür dass kovarianten Rückgabewerte nur von rohen Pointern unterstützt werden?
hustbaer schrieb:
Ich sehe kein Problem.
Das ist gut zu hören

Dann werd ich mal ein bisschen weiter experimentieren

-
happystudent schrieb:
hustbaer schrieb:
Richtig.
Auch dabei hilft CRTP (+ non-virtual-interface):
Das non-virtual-interface sieht interessant aus, kannte ich noch nicht... Btw, gibts eigentlich einen Grund dafür dass kovarianten Rückgabewerte nur von rohen Pointern unterstützt werden?
Das ist eine Art Hack im Typsystem, der eben nur für rohe Pointer ausgelegt ist.
-
happystudent schrieb:
Das non-virtual-interface sieht interessant aus, kannte ich noch nicht...
Dann solltest du unbedingt den Artikel von Herb Sutter zu dem Them lesen - einer der C++ "Standardartikel" die mMn. jeder kennen sollte:
http://www.gotw.ca/publications/mill18.htmhappystudent schrieb:
Btw, gibts eigentlich einen Grund dafür dass kovarianten Rückgabewerte nur von rohen Pointern unterstützt werden?
Grund dafür gibt es ziemlich sicher, aber ich kann ihn dir nicht sagen.
Ich kann höchstens vermuten...z.B. dass bei der Standardisierung entschieden wurde dass der Aufwand nicht dafür steht. Das war ja damals 1998, da waren die Compiler noch LANGE nicht so weit wie heute.
Oder sie wollten vermeiden dass dabei userdefinierter Code (=der Conversion-Konstruktor) ausgeführt werden muss. Es muss dazu ja vom Compiler eine "Zwischenfunktion" erstellt werden die den originalen Returnwert hat und dann die Funktion des Users aufruft.
In dieser "Zwischenfunktion" würde dann der Conversion-Konstruktor ausgeführt.
Nicht dass das technisch ein Problem wäre, aber vielleicht meinten sie einfach dass das "komisch" wäre.Der Upcast eines rohen Zeigers ist dagegen trivial und
noexceptund erfordert vor allem keinen userdefinierter Code.ps: Jetzt wo ich die Antwort von Nathan lese...
Bei userdefinierten Konvertierungen kann man ja nicht zwischen Covarianz und dem viel allgemeineren Konzept "implizite Konvertierbarkeit" unterscheiden.
DAS wird vermutlich der Grund gewesen sein.
Also dass das Committee es nicht richtig fand das allgemein für konvertierbare Typen zu erlauben.ps2:
Nathan schrieb:
Das ist eine Art Hack im Typsystem, der eben nur für rohe Pointer ausgelegt ist.
So kann man das mMn. auch nicht sagen. Das Typsystem wird dadurch ja nicht modifiziert. Die dazu nötigen Änderungen im Standard sollten sich auf die Stellen beschränken wo virtuelle Funktionen beschreiben werden. Kann mir nicht vorstellen dass dazu Änderrungen im Typsystem nötig wären.
-
hustbaer schrieb:
ps2:
Nathan schrieb:
Das ist eine Art Hack im Typsystem, der eben nur für rohe Pointer ausgelegt ist.
So kann man das mMn. auch nicht sagen. Das Typsystem wird dadurch ja nicht modifiziert. Die dazu nötigen Änderungen im Standard sollten sich auf die Stellen beschränken wo virtuelle Funktionen beschreiben werden. Kann mir nicht vorstellen dass dazu Änderrungen im Typsystem nötig wären.
Ich meinte lediglich, dass man dadurch eine virtuelle Funktion mit andere Signatur überschreibt, als eigentlich da ist, was sonst nicht geht. Deswegen ein "Hack" im Typsystem, da der return type auf einmal auch einen anderen Wert haben kann.
-
Je näher ich darüber nachdenke, desto "hackiger" kommt es mir auch vor. Ob man jetzt "im Typsystem" sagen will oder nicht... OK, ganz unpassend ist es nicht.
Was ich z.B. nicht wusste (grad ausprobiert), aber irgendwie vernünftig ist, ist...
Dass man in einer Klasse die von "Derived" abgeleitet ist dann auch mindestens einen "Derived*" zurückgeben muss, wenn "Derived" den Returntyp covariant verändert hat.Also
struct Base { virtual Base* clone() = 0; }; struct Derived : Base { virtual Derived* clone() { return new Derived(); } }; struct MoreDerived : Derived { virtual Derived* clone() // Muss MINDESTENS Derived* sein, weil Derived::clone den Typ auf Derived* "runtergezogen" hat { return new MoreDerived(); } };Und das ist schon irgendwie "hackig".
Wobei mir auf die Schnelle auch keine bessere Lösung einfällt.
-
Argh. Das passiert wenn man schnell was hinschreibt und nicht genug darüber nachdenkt.
Meine tolle "non-virtual-interface" Implementierung war natürlich total kaputt.
Hab's korrigiert.