webentwicklung-frage-antwort-db.com.de

Initialisieren eines std :: unique_ptr durch Übergeben der Adresse des Zeigers

Ich erstelle eine Klasse, die mit etwas Windows-API-Code interops. Jetzt wird einer der Zeiger, die ich initialisieren muss, durch Aufrufen einer systemeigenen Funktion ausgeführt, die ihn initialisiert.

Meine Zeiger sind vom Typ std::unique_ptr mit einem benutzerdefinierten Deleter, der die bereitgestellte WinAPI-Deleter-Funktion aufruft. Ich kann jedoch nicht das unique_ptr mit dem Operator & address-of an die init-Funktion übergeben. Warum?

Ich habe ein Beispiel erstellt, das mein Problem veranschaulicht:

#include <memory>

struct foo
{
   int x;
};

struct custom_deleter {};

void init_foo(foo** init)
{
  *init = new foo();
}

int main()
{
   std::unique_ptr<foo, custom_deleter> foo_ptr;

   init_foo(&foo_ptr);
}

Der Compiler bellt und sagt:

source.cpp: In function 'int main()':
source.cpp:19:21: error: cannot convert 'std::unique_ptr<foo, custom_deleter>*' to 'foo**' for argument '1' to 'void init_foo(foo**)'
22
Tony The Lion

Irgendwo unter den Abdeckungen hat unique_ptr<foo> ein Datenelement vom Typ foo*.

Es ist jedoch nicht legitim, dass ein Benutzer der Klasse dieses Datenmitglied direkt ändert. Dies würde die Klasseninvarianten von unique_ptr nicht unbedingt beibehalten, insbesondere den alten Zeigerwert (falls vorhanden) nicht freigeben. In Ihrem speziellen Fall brauchen Sie dies nicht, da der vorherige Wert 0 ist, aber im Allgemeinen sollte dies der Fall sein.

Aus diesem Grund bietet unique_ptr keinen Zugriff auf das Datenelement, sondern nur auf eine Kopie seines Werts (über get() und operator->). Sie können keinen foo** aus Ihrem unique_ptr herausholen.

Sie könnten stattdessen schreiben:

foo *tmp;
init_foo(&tmp);
std::unique_ptr<foo, custom_deleter> foo_ptr(tmp);

Dies ist aus dem gleichen Grund ausnahmesicher, dass std::unique_ptr<foo, custom_deleter> foo_ptr(new foo()); ausnahmesicher ist: unique_ptr garantiert, dass alles, was Sie an seinen Konstruktor übergeben, mit dem Deleter gelöscht wird.

Übrigens, benötigt custom_deleter keine operator()(foo*)? Oder habe ich etwas verpasst?

19
Steve Jessop

Steve hat bereits erklärt, was das technische Problem ist, jedoch geht das zugrunde liegende Problem viel tiefer: Der Code verwendet eine Redewendung, die hilfreich ist, wenn Sie mit nackten Zeigern arbeiten. Warum führt dieser Code zunächst eine zweistufige Initialisierung durch (zuerst das Objekt erstellen, dann initialisieren)? Da Sie intelligente Zeiger verwenden möchten, sollten Sie den Code sorgfältig anpassen: 

foo* init_foo()
{
  return new foo();
}

int main()
{
   std::unique_ptr<foo, custom_deleter> foo_ptr( init_foo() );

}

Es wäre natürlich besser, init_foo() in create_foo() umzubenennen und einen std::unique_ptr<foo> direkt zurückgeben zu lassen. Wenn Sie die zweistufige Initialisierung verwenden, ist es häufig ratsam, die Verwendung einer Klasse für den Datenumbruch in Betracht zu ziehen. 

5
sbi

Sie können den folgenden Trick verwenden:

template<class T>
class ptr_setter
{
public:
    ptr_setter(T& Ptr): m_Ptr{Ptr} {}
    ~ptr_setter() { m_Ptr.reset(m_RawPtr); }

    ptr_setter(const ptr_setter&) = delete;
    ptr_setter& operator=(const ptr_setter&) = delete;

    auto operator&() { return &m_RawPtr; }

private:
    T& m_Ptr;
    typename T::pointer m_RawPtr{};
};


// Macro will not be needed with C++17 class template deduction.
// If you dislike macros (as all normal people should)
// it's possible to replace it with a helper function,
// although this would make the code a little more complex.

#define ptr_setter(ptr) ptr_setter<decltype(ptr)>(ptr)

und dann:

std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&ptr_setter(foo_ptr));
0
Alex Alabuzhev

Ich habe schließlich einen Ansatz gefunden, der es erlaubt, unique_ptr's mit einem Code wie folgt zu initialisieren:

struct TOpenSSLDeleter { ... }; // Your custom deleter
std::unique_ptr<EVP_MD_CTX, TOpenSSLDeleter> Ctx;
...
Ctx = MakeUnique(EVP_MD_CTX_create()); // MakeUnique() accepts raw pointer

Und hier ist die Lösung:

template <class X>
struct TUniquePtrInitHelper {
   TUniquePtrInitHelper(X *Raw) noexcept {
      m_Raw = Raw;
   }
   template <class T, class D>
   operator std::unique_ptr<T, D>() const noexcept {
      return std::unique_ptr<T, D>(m_Raw);
   }

private:
   X *m_Raw;
};
template <class X>
TUniquePtrInitHelper<X> MakeUnique(X *Raw) noexcept {
   return {Raw};
}
0
DimanNe