2016-03-20 14 views
9

Ich verstehe, dass static_pointer_cast mit unique_ptr Verwendung zu einem gemeinsamen Besitz der enthaltenen Daten führen würde.
Mit anderen Worten, was ich möchte ist tun:Alternativen von static_pointer_cast für unique_ptr

unique_ptr<Base> foo = fooFactory(); 
// do something for a while 
unique_ptr<Derived> bar = static_unique_pointer_cast<Derived>(foo); 

Wie dem auch sei, dass die Ergebnisse mit zwei unique_ptr zu tun, die nie zur gleichen Zeit existieren sollte, so ist es einfach verboten.
Recht, macht es Sinn, absolut, das ist, warum in der Tat ist es nicht so etwas wie static_unique_pointer_cast existiert.

Bisher, in Fällen, in denen ich Zeiger auf diese Basisklassen speichern möchte, aber ich muss sie auch in einige abgeleitete Klassen umwandeln (als Beispiel, stellen Sie sich ein Szenario mit Typ löschen), habe ich shared_ptr s verwendet wegen dem, was ich oben erwähnt habe.

Wie auch immer, ich vermute, es gibt Alternativen zu shared_ptr s für ein solches Problem oder wenn sie wirklich die beste Lösung in diesem Fall sind.

+0

' static_pointer_cast' ist nur für ein Argument vom Typ 'std :: shared_ptr ' definiert - es ist überhaupt nicht verwendbar mit 'unique_ptr' –

+0

@MM Ja, ich weiß, ich habe fast das gleiche in der Frage geschrieben, dass es kein Äquivalent gibt für das 'unique_ptr'. – skypjack

+0

Warum führt Typ-Löschung zu Downcasting? Wenn Sie Ihre Basisklasse in einen abgeleiteten Typ umwandeln müssen, ist es meistens, abgesehen von sehr wenigen Fällen, ein Designproblem, das anders gelöst werden sollte. – Jens

Antwort

16

Raw Zeiger

Die Lösung für Ihr Problem ist die rohe (nicht-besitzenden) Zeiger zu bekommen und es werfen - dann lassen Sie einfach die Rohzeiger den Gültigkeitsbereich verlassen und lassen Sie die restlichen unique_ptr<Base> constrol die Lebensdauer der eigenes Objekt.

So:

unique_ptr<Base> foo = fooFactory(); 

{ 
    Base* tempBase = foo.get(); 
    Derived* tempDerived = static_cast<Derived*>(tempBase); 
} //tempBase and tempDerived go out of scope here, but foo remains -> no need to delete 

Unique_pointer_cast

Die andere Option ist die release() Funktion von unique_ptr zu verwenden, um es in einer anderen unique_ptr zu wickeln.

Gefällt Ihnen dieses

template<typename TO, typename FROM> 
unique_ptr<TO> static_unique_pointer_cast (unique_ptr<FROM>&& old){ 
    return unique_ptr<TO>{static_cast<TO*>(old.release())}; 
    //conversion: unique_ptr<FROM>->FROM*->TO*->unique_ptr<TO> 
} 

unique_ptr<Base> foo = fooFactory(); 

unique_ptr<Derived> foo2 = static_unique_pointer_cast<Derived>(std::move(foo)); 

Denken Sie daran, dass dies der alte Zeiger foo

Referenz aus rohen Zeiger

Nur der Vollständigkeit halber der Antwort in Abrede stellen, wurde diese Lösung tatsächlich als eine kleine Änderung vorgeschlagen der rohen Zeiger vom OP in den Kommentaren.

Ähnlich wie bei der Verwendung von rohen Zeigern kann man die rohen Zeigern umwandeln und dann eine Referenz aus ihnen erstellen, indem man sie ableitet. In diesem Fall ist es wichtig, dass die Lebensdauer der erzeugten Referenz garantiert nicht die Lebensdauer des unique_ptr nicht übersteigt.

Probe:

unique_ptr<Base> foo = fooFactory(); 
Derived& bar = *(static_cast<Derived*>(foo.get())); 
//do not use bar after foo goes out of scope 
+1

Warum nicht umgekehrt die Reihenfolge dieser Template-Parameter und lassen Sie 'FROM' ableiten, genau wie in' std :: static_pointer_cast'? – aschepler

+0

@Anedar Ja, offensichtlich möchte ich mich nicht mit rohen Zeigern beschäftigen, aber du hast mir trotzdem einen guten Hinweis gegeben ... Die Implementierung von 'static_unique_pointer_cast' ist interessant, passt aber leider nicht gut zu der Idee (ausgedrückt in der Frage) des Speicherns dieser Zeiger, da sie nach der Ungültigkeitserklärung für zukünftige Verwendungen * nach der aktuellen Verwendung neu initialisiert werden müssen, so dass es zu einer großen Menge an Arbeit um einen Container herum führen würde. – skypjack

+0

guten Punkt, @ascheppler, ich habe das hinzugefügt. – Anedar

6

Ich verstehe, dass mit unique_ptr mit static_pointer_cast zu einem gemeinsamen Besitz der enthaltenen Daten führen würde.

Nur wenn Sie es schlecht definieren. Die offensichtliche Lösung wäre, dass das Eigentumsrecht übertragen wird, sodass das Quellobjekt leer ist.

Wenn Sie nicht das Eigentum übertragen wollen dann nur einen rohen Zeiger verwenden.

Oder wenn Sie zwei Besitzer wollen dann verwenden Sie shared_ptr.

Es scheint, dass Ihre Frage nur teilweise über die eigentliche Cast-Operation, und teilweise nur das Fehlen einer klaren Besitz-Politik für den Zeiger. Wenn Sie mehrere Eigentümer benötigen, unabhängig davon, ob sie denselben Typ verwenden oder ob ein anderer Typ verwendet wird, sollten Sie unique_ptr nicht verwenden.

Wie auch immer, das Ergebnis mit zwei unique_ptr, die nie gleichzeitig existieren sollte, so ist es einfach verboten.
Richtig, es macht Sinn, absolut, deshalb gibt es in der Tat nichts wie static_unique_pointer_cast.

Nein, das ist nicht der Grund, warum es nicht existiert. Es existiert nicht, weil es trivial ist, es selbst zu schreiben, wenn Sie es brauchen (und solange Sie es vernünftigen Semantiken des einzigartigen Eigentums geben). Holen Sie einfach den Zeiger mit release() werfen Sie es, und legen Sie es in einem anderen unique_ptr. Einfach und sicher.

Das ist nicht der Fall für die shared_ptr ist, wo die „offensichtliche“ Lösung nicht das Richtige tut:

shared_ptr<Derived> p2(static_cast<Derived*>(p1.get()); 

, dass zwei verschiedene shared_ptr Objekte erzeugen würde, die die gleichen Zeiger besitzen, aber don keine Eigentumsrechte teilen (dh sie würden beide versuchen, sie zu löschen, was zu undefiniertem Verhalten führt).

Wenn shared_ptr zum ersten Mal genormt wurde, gab es keinen sicheren Weg dies zu tun, daher wurden static_pointer_cast und die zugehörigen Casting-Funktionen definiert. Sie benötigten Zugriff auf die Implementierungsdetails der shared_ptr Buchhaltungsinformationen, um zu arbeiten.

Doch während der C++ 11 Standardisierungsprozess shared_ptr wurde durch die Zugabe des „Aliasing-Konstruktor“ erweitert, die Sie die Besetzung einfach und sicher tun können:

shared_ptr<Derived> p2(p1, static_cast<Derived*>(p1.get()); 

Wenn diese Funktion immer gewesen war Teil von shared_ptr dann ist es möglicherweise, vielleicht sogar wahrscheinlich, dass static_pointer_cast nie definiert worden wäre.

+0

Nun, * Es existiert nicht, weil es trivial ist, es selbst zu schreiben * könnte auch auf einige andere Teile (die existieren) der STL tatsächlich angewendet werden, aber ich habe den Punkt Ihrer Antwort bekommen. +1 – skypjack

+0

Sie haben das Ende des Satzes verpasst, "wenn Sie es brauchen". Es lohnt sich, in den Standard zu investieren, wenn es für die Benutzer schwierig/unmöglich ist, sie korrekt zu schreiben (was ursprünglich für shared_ptr-Umwandlungen galt) oder wenn sie einfach sind, aber jeder es immer wieder implementiert. Dies gilt auch für unique_ptr-Umwandlungen, da es einfach ist, aber nicht oft benötigt wird. –

+1

Ich stimme zu, ehrlich gesagt denke ich, dass ich vor einem XY-Problem stehe, aber trotzdem war die Frage für mich interessant zu wissen, was andere getan hätten, wenn sie ich wären. – skypjack

0

Ich möchte etwas zur vorherigen Antwort von Anedar hinzufügen, die die release() Mitglieds-Methode der gegebenen std::unique_ptr<U> aufruft. Wenn man auch einen dynamic_pointer_cast (zusätzlich zu einem static_pointer_cast) zum Umwandeln von std::unique_ptr<U> zu std::unique_ptr<T> implementieren will, muss man sicherstellen, dass die durch den eindeutigen Zeiger geschützte Ressource richtig freigegeben wird, falls der dynamic_cast fehlschlägt (d.h. nullptr zurückgibt). Andernfalls tritt ein Speicherverlust auf.

-Code:

#include <iostream> 
#include <memory> 

template< typename T, typename U > 
inline std::unique_ptr<T> dynamic_pointer_cast(std::unique_ptr<U> &&ptr) { 
    U * const stored_ptr = ptr.release(); 
    T * const converted_stored_ptr = dynamic_cast< T * >(stored_ptr); 
    if (converted_stored_ptr) { 
     std::cout << "Cast did succeeded\n"; 
     return std::unique_ptr<T>(converted_stored_ptr); 
    } 
    else { 
     std::cout << "Cast did not succeeded\n"; 
     ptr.reset(stored_ptr); 
     return std::unique_ptr<T>(); 
    } 
} 

struct A { 
    virtual ~A() = default; 
}; 
struct B : A { 
    virtual ~B() { 
     std::cout << "B::~B\n"; 
    } 
}; 
struct C : A { 
    virtual ~C() { 
     std::cout << "C::~C\n"; 
    } 
}; 
struct D { 
    virtual ~D() { 
     std::cout << "D::~D\n"; 
    } 
}; 

int main() { 

    std::unique_ptr<A> b(new B); 
    std::unique_ptr<A> c(new C); 
    std::unique_ptr<D> d(new D); 

    std::unique_ptr<B> b1 = dynamic_pointer_cast< B, A >(std::move(b)); 
    std::unique_ptr<B> b2 = dynamic_pointer_cast< B, A >(std::move(c)); 
    std::unique_ptr<B> b3 = dynamic_pointer_cast< B, D >(std::move(d)); 
} 

Ausgabe (Online-Bestellung möglich):

Cast did succeeded 
Cast did not succeeded 
Cast did not succeeded 
B::~B 
D::~D 
C::~C 

Die Destruktoren von C und D wird nicht aufgerufen werden, wenn man verwendet:

template< typename T, typename U > 
inline std::unique_ptr<T> dynamic_pointer_cast(std::unique_ptr<U> &&ptr) { 
    return std::unique_ptr<T>(dynamic_cast< T * >(ptr.release())); 
} 
Verwandte Themen