Wie kann ich ein polymorphes Objekt auf dem Stapel erstellen?

8

Wie ordne ich ein polymorphes Objekt auf dem Stapel zu? Ich versuche, etwas Ähnliches zu tun (versucht, Heap-Zuweisung mit neuen zu vermeiden):

%Vor%     
user1040229 15.08.2012, 17:32
quelle

12 Antworten

7

Sie können eine einzelne Funktion nicht so strukturieren, dass sie so funktioniert, da automatische oder temporäre Objekte, die in einem konditionalen Block erstellt wurden, ihre Lebensdauern nicht in den umgebenden Block erweitern können.

Ich würde vorschlagen, das polymorphe Verhalten in eine separate Funktion umzuwandeln:

%Vor%     
Mike Seymour 15.08.2012, 17:47
quelle
7

Disclaimer: Ich halte das definitiv nicht für eine gute Lösung. Die guten Lösungen bestehen darin, entweder das Design zu überdenken (vielleicht ist OO-Polymorphismus hier nicht gerechtfertigt, da es eine beschränkte Anzahl von Möglichkeiten gibt?) Oder eine zweite Funktion zu verwenden, um das polymorphe Objekt durch Referenz weiterzuleiten.

Aber da andere Leute diese Idee erwähnten, aber Details falsch verstanden haben, poste ich diese Antwort, um zu zeigen, wie man es richtig macht. Hoffentlich verstehe ich es richtig.

Es ist klar, dass die Anzahl der möglichen Typen begrenzt ist. Dies bedeutet, dass eine diskriminierte Union wie boost::variant das Problem lösen könnte, auch wenn es nicht hübsch ist:

%Vor%

Die Tatsache, dass Sie jetzt Dinge wie statische Besucher verwenden können, ist eine Sache, wenn die Dinge, die mich dazu bringen zu denken, dass dies keine gute Verwendung von OO-Polymorphismus ist.

Wenn Sie anstelle einer vorgefertigten Lösung die neue Platzierung per Hand verwenden möchten, wie in anderen Antworten vorgeschlagen, gibt es einige Dinge, die beachtet werden müssen, da einige der Eigenschaften von regulären automatischen Objekten verloren gehen:

  • Der Compiler gibt uns nicht mehr die richtige Größe und Ausrichtung;
  • Wir erhalten keinen automatischen Aufruf an die Destruktoren;

In C ++ 11 sind beide einfach mit aligned_union bzw. unique_ptr zu reparieren.

%Vor%

Für Compiler, die diese Funktionen nicht unterstützen, können Sie Alternativen erhalten: Boost enthält aligned_storage und alignment_of traits, die verwendet werden können, um aligned_union ; und unique_ptr kann durch eine Art Schutzklasse ersetzt werden.

Nun, da das aus dem Weg ist, nur damit es klar ist, tu das nicht und gib einfach ein temporäres Element an eine andere Funktion weiter, oder besuche das Design ganz neu.

    
R. Martinho Fernandes 15.08.2012 17:39
quelle
4

Wenn B Ihre Basistypen sind, sind D1, D2 und D3 Ihre abgeleiteten Typen:

%Vor%

Wenn Sie den Platz der drei abgeleiteten Objekte nicht verschwenden möchten, könnten Sie Ihre Methode in zwei Teile aufteilen; der Teil, der den gewünschten Typ auswählt, und der Teil der Methode, der darauf angewendet wird. Nachdem Sie entschieden haben, welchen Typ Sie benötigen, rufen Sie eine Methode auf, die dieses Objekt zuweist, einen Zeiger darauf erstellt und die zweite Hälfte der Methode aufruft, um die Arbeit an dem dem Stack zugeordneten Objekt abzuschließen.

    
Sniggerfardimungus 15.08.2012 17:35
quelle
2

Sie können keine polymorphe lokale Variable

erstellen

Sie können keine polymorphe lokale Variable erstellen, da eine Unterklasse B von A möglicherweise mehr Attribute als A hat, also mehr Platz einnimmt, sodass der Compiler genügend Speicherplatz für die größte Unterklasse reservieren muss von A .

  1. Falls Sie Dutzende Unterklassen haben und eine davon eine große Anzahl an Attributen hat, würde dies viel Platz verschwenden.
  2. Falls Sie der lokalen Variablen eine Instanz einer Unterklasse von A , die Sie als Parameter erhalten haben, übergeben und Ihren Code in eine dynamische Bibliothek einfügen, könnte der damit verknüpfte Code eine Unterklasse größer als die in deklarieren Ihre Bibliothek, so dass der Compiler sowieso nicht genug Speicherplatz auf dem Stack zugewiesen hätte.

Ordnen Sie also selbst Platz dafür zu

Wenn Sie Placement neu verwenden, können Sie das Objekt in einem Bereich initialisieren, den Sie auf andere Weise zugewiesen haben:

Diese Techniken benötigen jedoch möglicherweise viel zusätzlichen Speicherplatz und funktionieren nicht, wenn Sie eine Referenz (Zeiger) auf eine zur Kompilierungszeit unbekannte Unterklasse von A erhalten, die größer ist als die Typen, die Sie verwenden erklärt.

Die von mir vorgeschlagene Lösung besteht darin, für jede Unterklasse eine Art Factory-Methode zu verwenden, die eine angegebene Funktion mit einem Zeiger auf eine dem Stack zugeordnete Instanz der angegebenen Subklasse aufruft. Ich habe der Signatur der angegebenen Funktion einen zusätzlichen void * -Parameter hinzugefügt, so dass man beliebige Daten übergeben kann.

@MooingDuck schlug diese Implementierung mithilfe von Vorlagen und C ++ 11 in einem Kommentar unten vor. Falls Sie dies für Code benötigen, der nicht von C ++ 11-Features profitieren kann, oder für einfachen C-Code mit Strukturen anstelle von Klassen (wenn struct B ein erstes Feld vom Typ struct A hat, kann es manipuliert werden etwas wie ein "substruct" von A ), dann wird meine Version unten den Trick machen (aber ohne typsicher zu sein).

Diese Version arbeitet mit neu definierten Unterklassen, solange sie die Factory-like-Methode ugly implementieren und eine konstante Stapelmenge für die Rücksprungadresse und andere von dieser Zwischenfunktion benötigte Informationen plus die Größe verwenden einer Instanz der angeforderten Klasse, aber nicht die Größe der größten Unterklasse (es sei denn, Sie wählen diese).

%Vor%

Bis dahin, ich denke, wir könnten die Kosten einer einfachen malloc überwogen haben, also könnten Sie es doch benutzen.

    
Georges Dupéron 15.08.2012 17:34
quelle
2

Ich habe eine generische Vorlage dafür geschrieben. Vollständiger Code verfügbar hier (es wurde für den Umfang dieser Frage zu aufwendig). Das StackVariant-Objekt enthält einen Puffer der Größe des größten Typs unter den bereitgestellten Typen sowie die größte Ausrichtung. Das Objekt wird auf dem Stapel unter Verwendung einer 'Platzierung neu' und des Operators konstruiert - & gt; () wird für den polymorphen Zugriff verwendet, um die Indirektion vorzuschlagen. Außerdem ist es wichtig, dass ein virtueller Detor definiert wird, sollte er bei der Zerstörung des Objekts auf dem Stack aufgerufen werden, so tut der Template-Detor genau das mit einer SFINAE-Definition.

(siehe Verwendungsbeispiel und Ausgabe unten):

%Vor%

Anwendungsbeispiel:

%Vor%

Einige Dinge zu beachten:

  1. Ich habe es geschrieben, während ich versucht habe, Heap-Zuweisungen in leistungskritischen Funktionen zu vermeiden, und es hat den Job gemacht - 50% Geschwindigkeitsgewinne.
  2. Ich habe es geschrieben, um die eigenen polymorphen Mechanismen von C ++ zu nutzen. Davor war mein Code voll von Switch-Cases wie die vorherigen Vorschläge hier.
Shlomi Loubaton 13.06.2017 22:06
quelle
1

Sie können es mit neuer Platzierung tun. Dadurch werden die Elemente auf dem Stapel im Speicher im Puffer abgelegt. Diese Variablen sind jedoch nicht automatisch. Der Nachteil ist, dass Ihre Destruktoren nicht automatisch ausgeführt werden, sondern Sie sie genau so zerstören müssen, wie Sie sie erstellt haben, wenn sie den Gültigkeitsbereich verlassen.

Eine sinnvolle Alternative zum manuellen Aufrufen des Destruktors besteht darin, Ihren Typ in einen Smart Pointer einzubinden, wie unten gezeigt:

%Vor%     
Chad 15.08.2012 17:40
quelle
0

Polymorphismus funktioniert nicht mit Werten, Sie benötigen eine Referenz oder einen Zeiger. Sie können einen konstanten Verweis auf ein temporäres Objekt polymorph verwenden und es wird die Lebensdauer eines Stapelobjekts haben.

%Vor%

Wenn Sie das Objekt ändern müssen, müssen Sie es dynamisch zuweisen (es sei denn, Sie verwenden Microsofts nicht standardmäßige Erweiterung, mit der Sie ein temporäres Objekt an eine nicht konstante Referenz binden können).

    
Dirk Holsopple 15.08.2012 17:37
quelle
0

Eine Kombination aus einem char -Array und einem Placement new würde funktionieren.

%Vor%     
Praetorian 15.08.2012 17:40
quelle
0

Um Ihre Frage genau zu beantworten - was Sie jetzt tun - also a = A(); und a = B() und a = C() , aber diese Objekte sind in Scheiben geschnitten.

Um polymorphes Verhalten zu erreichen mit dem Code, den Sie haben , ich ', fürchte, das ist nicht möglich. Der Compiler muss vorher die Größe des Objekts kennen. Es sei denn, Sie haben Referenzen oder Zeiger.

Wenn Sie einen -Zeiger verwenden , müssen Sie sicherstellen, dass er nicht aufhängt:

%Vor%

funktioniert nicht, weil obj den Gültigkeitsbereich verlässt. Sie sind also übrig mit:

%Vor%

Das ist natürlich verschwenderisch.

Bei Referenzen ist es ein wenig komplizierter, da sie bei der Erstellung zugewiesen werden müssen, und Sie können keine Provisorien verwenden (es sei denn, es handelt sich um const -Referenz). Sie benötigen also wahrscheinlich eine Factory, die eine persistente Referenz zurückgibt.

    
Luchian Grigore 15.08.2012 17:40
quelle
0
  

versucht, Heap-Zuweisung mit neuen zu vermeiden)?

In diesem Fall erstellen Sie wie gewohnt Objekte auf dem Stack und weisen dem Basiszeiger eine Adresse zu . Aber denken Sie daran, wenn dies innerhalb einer Funktion ausgeführt wird, übergeben Sie die Adresse nicht als Rückgabewert, da der Stapel nach dem Funktionsaufruf wieder verschwindet.

Das ist also schlecht.

%Vor%     
Ankush 15.08.2012 17:42
quelle
0

Es ist möglich, aber es ist ein Los Aufwand sauber zu tun (ohne manuelle Platzierung neuer und verfügbarer Rohpuffer, das ist).

Sie betrachten etwas wie Boost.Variant , das geändert wurde Beschränke die Typen auf eine Basisklasse und einige abgeleitete Klassen, und , um einen polymorphen Verweis auf den Basistyp verfügbar zu machen.

Dieses Ding ( PolymorphicVariant ?) würde alle neuen Sachen für dich einpacken (und auch für die sichere Zerstörung sorgen).

Wenn es wirklich das ist, was du willst, lass es mich wissen und ich werde dir einen Anfang geben. Wenn Sie dieses Verhalten nicht wirklich genau brauchen, ist der Vorschlag von Mike Seymour praktischer.

    
Useless 15.08.2012 17:50
quelle
0

Führen Sie dieses kurze Programm aus, und Sie werden sehen, warum polymorphe Objekte auf dem Stapel nicht gut funktionieren. Wenn Sie ein Stack-Objekt eines abgeleiteten Typs erstellen, der unbekannt ist und erwartet, dass er von einem Funktionsaufruf zurückgegeben wird, passiert, dass das Objekt zerstört wird, wenn die aufrufende Funktion den Gültigkeitsbereich verlässt. Daher lebt das Objekt nur so lange, wie diese Funktion innerhalb des Umfangs ist. Um ein gültiges Objekt zurückzugeben, das die aufrufende Funktion überlebt, müssen Sie den Heap verwenden. Dies wird mit dieser einfachen Hierarchie und zwei Versionen derselben Funktion mit einer switch-Anweisung demonstriert, mit der Ausnahme, dass der eine Stapel den Stapel und der andere den Stapel übernimmt. Sehen Sie sich die Ausgabe von beiden Implementierungen an und schauen Sie, welche Methoden aufgerufen werden, von welcher Klasse sie aufgerufen werden und wann sie aufgerufen werden.

%Vor%

Ausgabe:

%Vor%

Ich sage nicht, dass es nicht getan werden kann; Ich sage nur die Vorbehalte, wenn ich versuche, dies zu tun. Es mag schlecht beraten sein, etwas Ähnliches zu tun. Ich kenne keine Möglichkeit, dies zu tun, es sei denn, Sie haben eine Wrapperklasse, die den Stack zugeordnete Objekte enthält, um ihre Lebensdauer zu verwalten. Ich muss versuchen, daran zu arbeiten, um zu sehen, ob ich etwas von der Art aufwerfen kann.

    
Francis Cugler 18.06.2017 08:49
quelle

Tags und Links