Je crée une classe qui interops avec du code API Windows. Désormais, l’un des pointeurs que j’initialise doit être initialisé en appelant une fonction native qui l’initialise.
Mes pointeurs sont de type std::unique_ptr
avec un suppresseur personnalisé, qui appelle la fonction WinAPI deleter fournie. Cependant, je ne peux pas transmettre unique_ptr avec l'opérateur & address-of à la fonction init. Pourquoi?
J'ai créé un exemple qui illustre mon problème:
#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);
}
Le compilateur aboie et dit:
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**)'
Quelque part sous les couvertures, unique_ptr<foo>
a un membre de données de type foo*
.
Cependant, il n'est pas légitime qu'un utilisateur de la classe modifie directement ce membre de données. Cela ne préserverait pas nécessairement les invariants de classe de unique_ptr
, en particulier il ne libérerait pas l'ancienne valeur du pointeur (le cas échéant). Dans votre cas particulier, vous n'avez pas besoin de cela, car la valeur précédente est 0, mais en général, cela devrait arriver.
Pour cette raison, unique_ptr
ne permet pas d'accéder au membre de données, mais uniquement à une copie de sa valeur (via get()
et operator->
). Vous ne pouvez pas obtenir un foo**
de votre unique_ptr
.
Vous pourriez plutôt écrire:
foo *tmp;
init_foo(&tmp);
std::unique_ptr<foo, custom_deleter> foo_ptr(tmp);
Ceci est une exception pour la même raison que std::unique_ptr<foo, custom_deleter> foo_ptr(new foo());
est une exception: unique_ptr
garantit que tout ce que vous transmettez à son constructeur sera finalement supprimé à l'aide du suppresseur.
Btw, custom_deleter
n'a-t-il pas besoin d'une operator()(foo*)
? Ou ai-je raté quelque chose?
Steve a déjà expliqué le problème technique, cependant, le problème sous-jacent va beaucoup plus loin: le code emploie un idiome utile lorsque vous traitez avec des pointeurs nus. Pourquoi ce code effectue-t-il une initialisation en deux étapes (commence par créer l'objet, puis l'initialise) en premier lieu? Puisque vous voulez utiliser des pointeurs intelligents, je vous suggère de bien adapter le code:
foo* init_foo()
{
return new foo();
}
int main()
{
std::unique_ptr<foo, custom_deleter> foo_ptr( init_foo() );
}
Bien sûr, il serait préférable de renommer init_foo()
en create_foo()
et de lui renvoyer un std::unique_ptr<foo>
directement. En outre, lorsque vous utilisez l'initialisation en deux étapes, il est souvent conseillé d'utiliser une classe pour envelopper les données.
Vous pouvez utiliser le truc suivant:
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)
et alors:
std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&ptr_setter(foo_ptr));
J'ai finalement proposé une approche permettant d'initialiser unique_ptr avec un code comme celui-ci:
struct TOpenSSLDeleter { ... }; // Your custom deleter
std::unique_ptr<EVP_MD_CTX, TOpenSSLDeleter> Ctx;
...
Ctx = MakeUnique(EVP_MD_CTX_create()); // MakeUnique() accepts raw pointer
Et voici la solution:
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};
}