wake-up-neo.com

Alternativen von static_pointer_cast für unique_ptr

Ich verstehe, dass die Verwendung von static_pointer_cast mit unique_ptr zu einem gemeinsamen Besitz der enthaltenen Daten führen würde.
In anderen Worten, was ich gerne tun würde, ist:

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

Auf jeden Fall ergibt dies zwei unique_ptr, die niemals gleichzeitig existieren sollten, und ist daher einfach verboten.
Richtig, es macht Sinn, absolut, deshalb gibt es nichts wie static_unique_pointer_cast.

Bisher habe ich in Fällen, in denen Zeiger auf diese Basisklassen gespeichert werden sollen, aber auch in abgeleitete Klassen umgewandelt werden müssen (zum Beispiel ein Szenario mit Typlöschung), ich shared_ptrs wegen dessen verwendet, was ich ' oben schon erwähnt. 

Ich habe auf jeden Fall vermutet, ob es für ein solches Problem Alternativen zu shared_ptrs gibt oder ob sie in diesem Fall wirklich die beste Lösung sind.

17
skypjack

Rohe Zeiger

Die Lösung für Ihr Problem besteht darin, den unformatierten Zeiger zu erhalten und ihn umzuwerfen. Lassen Sie dann den unformatierten Zeiger aus dem Gültigkeitsbereich und lassen Sie den verbleibenden unique_ptr<Base> die Lebensdauer des zugehörigen Objekts bestimmen.

So was:

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 besteht darin, die Funktion release() von unique_ptr zu verwenden, um sie in ein anderes unique_ptr einzubinden.

So was

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 der alte Zeiger foo ungültig wird.

Referenz von Rohzeiger

Nur zur Vollständigkeit der Antwort wurde diese Lösung vom OP in den Kommentaren als kleine Modifikation der Rohzeiger vorgeschlagen.

Ähnlich wie bei der Verwendung von Rohzeigern können die Rohzeiger geworfen werden und dann durch Referenzieren eine Referenz erstellt werden. In diesem Fall muss sichergestellt werden, dass die Lebensdauer der erstellten Referenz die Lebensdauer des unique_ptr nicht überschreitet.

Probe:

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

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

Nur wenn Sie es schlecht definieren. Die naheliegende Lösung wäre, den Besitz zu übertragen, sodass das Quellobjekt leer bleibt.

Wenn Sie den Besitz nicht übertragen möchten, verwenden Sie einfach einen Rohzeiger.

Oder wenn Sie zwei Besitzer haben möchten, verwenden Sie shared_ptr.

Es scheint, als ob Ihre Frage nur zum Teil die eigentliche Besetzung betrifft und zum Teil nur eine klare Eigentümerpolitik für den Zeiger. Wenn Sie mehrere Besitzer benötigen, unabhängig davon, ob beide den gleichen Typ verwenden oder ob einer von ihnen in einen anderen Typ umgewandelt wird, sollten Sie unique_ptr nicht verwenden.

Dies führt jedoch zu zwei unique_ptr, die niemals gleichzeitig existieren sollten, also ist es einfach verboten.
Richtig, es macht Sinn, absolut, deshalb gibt es überhaupt nichts wie static_unique_pointer_cast.

Nein, deshalb existiert es nicht. Es gibt es nicht, weil es trivial ist, es selbst zu schreiben, wenn Sie es brauchen (und solange Sie ihm eine gesunde Semantik von einzigartigem Besitz geben). Holen Sie den Zeiger einfach mit release() cast und setzen Sie ihn in einen anderen unique_ptr. Einfach und sicher.

Dies ist nicht der Fall für shared_ptr, bei dem die "offensichtliche" Lösung nicht das Richtige tut:

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

Dadurch würden zwei verschiedene shared_ptr-Objekte erstellt, die zwar den gleichen Zeiger besitzen, jedoch nicht den gemeinsamen Besitz besitzen (d. H. Sie würden beide versuchen, ihn zu löschen, was zu undefiniertem Verhalten führt).

Als shared_ptr zum ersten Mal standardisiert wurde, gab es keine sichere Methode, so dass static_pointer_cast und die zugehörigen Casting-Funktionen definiert wurden. Sie brauchten Zugriff auf die Implementierungsdetails der shared_ptr-Buchhaltungsinformationen, um zu funktionieren.

Während des C++ 11-Standardisierungsprozesses wurde shared_ptr jedoch um den "Aliasing-Konstruktor" erweitert, mit dem Sie den Cast einfach und sicher ausführen können:

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

Wenn diese Funktion schon immer Bestandteil von shared_ptr gewesen wäre, wäre static_pointer_cast möglicherweise oder sogar wahrscheinlich nicht definiert worden.

7
Jonathan Wakely

Ich möchte der vorherigen Antwort von Anedar etwas hinzufügen, die die release() -Member-Methode des angegebenen std::unique_ptr< U > aufruft. Wenn auch ein dynamic_pointer_cast (zusätzlich zu einem static_pointer_cast) zum Konvertieren von std::unique_ptr< U > in std::unique_ptr< T > implementiert werden soll, muss sichergestellt werden, dass die durch den eindeutigen Zeiger überwachte Ressource ordnungsgemäß freigegeben wird, falls der dynamic_cast fehlschlägt (d. H. Eine 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 (mögliche Bestellung):

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

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

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()));
}
0
Matthias