ptr partagé avec des ressources non-pointeur
En C++11 est-il possible d'utiliser shared_ptr
pour contrôler les ressources non-pointeur?
Il est possible d'utiliser unique_ptr
pour gérer les ressources non-pointeurs. Ceci est fait en implémentant une classe Deleter personnalisée qui fournit:
- A
typedef {TYPE} pointer;
où {[5] } est le type de Ressource sans pointeur - {[6] } qui libère la ressource contrôlée
...et puis instancier un unique_ptr
avec le deleter personnalisé comme deuxième paramètre de modèle.
Par exemple, sous Windows, il est possible de créer un unique_ptr
qui gère un handle de contrôle de service . Ce type de poignée n'est pas libéré par l'appel de delete
, mais en appelant CloseServiceHandle()
. Voici un exemple de code qui fait ceci:
Deleter Personnalisé
struct SvcHandleDeleter
{
typedef SC_HANDLE pointer;
SvcHandleDeleter() {};
template<class Other> SvcHandleDeleter(const Other&) {};
void operator()(pointer h) const
{
CloseServiceHandle(h);
}
};
typedef std::unique_ptr<SC_HANDLE,SvcHandleDeleter> unique_sch;
Instanciation
unique_sch scm(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS));
Est-il possible d'utiliser shared_ptr
pour contrôler également une ressource non-pointeur?
Selon la documentation, il y a shared_ptr
les surcharges du constructeur {[28] } qui prennent la signifie fournir une classe Deleter personnalisée, mais aucun des constructeurs n'accepte un type de ressource qui n'est ni un pointeur ni un wrapper autour d'un pointeur.
Comment cela peut-il être fait?
4 réponses
Malheureusement, le besoin de shared_ptr
pour l'effacement de type rend impossible avec l'interface actuelle d'atteindre exactement ce que vous voulez. unique_ptr
parvient à le faire car il a des informations statiques sur le type de deleter réel, d'où il peut dessiner le type "pointeur" réel. Dans le cas de shared_ptr
, le type de suppression est perdu dans le processus d'effacement de type (c'est pourquoi vous ne pouvez pas le spécifier dans le modèle shared_ptr
).
Notez également que unique_ptr
ne fournit aucun constructeur de conversion comme le fait shared_ptr
(par exemple template<class Y> shared_ptr(Y* p)
). Il ne peut pas le faire parce que pointer
n'est pas nécessairement un vrai type de pointeur, et donc il ne peut pas restreindre ce qui peut être accepté (sauf peut-être à travers des SFINAE avec std::is_convertible_to
ou quelque chose comme ça... mais je m'égare).
Maintenant, une solution de contournement évidente consiste simplement à new
le handle de Ressource, aussi stupide que cela puisse paraître. :/
std::shared_ptr<SC_HANDLE> sp(new SC_HANDLE(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS)),
[](SC_HANDLE* p){ ::CloseServiceHandle(*p); delete p; });
Eh bien, shared_ptr appellera le destructeur de l'objet pointé vers dès que la dernière référence au pointeur est libérée, alors tout ce que la classe contient peut être libéré. Il suffit de faire une classe peut-être comme ceci:
struct SvcHandle
{
typedef SC_HANDLE pointer;
SvcHandle()
:M_handle(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS))
{ }
~SvcHandle()
{
CloseServiceHandle(M_handle);
}
private:
pointer M_handle;
};
Ensuite, créez simplement un pointeur partagé avec un nouveau SvcHandle. La gestion de la durée de vie du handle ira avec le shared_ptr-RAII à son meilleur.
Et ça?
auto scm = make_shared<unique_sch>(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS));
Unique_sch est la classe que vous avez mentionné dans votre question. Maintenant, utilisez scm comme un pointeur partagé vers votre ressource. Ce n'est pas la chose la plus agréable à déréférencer en cas de besoin, mais vous avez demandé si c'était possible.
Mais cela utilise toujours un pointeur. Comme on peut le voir dans la documentation, la classe unique_ptr prend une classe pointer en tant que paramètre constructeur, ce qui peut en fait être n'importe quoi. shared_ptr prend cependant un type comme le paramètre template et le transforme avec force en un pointeur vers ce type sur son constructeur:
Template shared_ptr ( Y* ptr, Deleter d);
Je pense qu'il est sûr de dire qu'il ne peut pas directement être utilisé pour gérer une ressource non-pointeur, car la classe shared_ptr suppose que sa ressource est un pointeur.
Pensez Non. Puisque la norme fournit de tels constructeurs pour shared_ptr et aucun autre.
// 20.7.2.2.1, constructors:
constexpr shared_ptr() noexcept;
template<class Y> explicit shared_ptr(Y* p);
template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d)
template <class D, class A> shared_ptr(nullptr_t p, D d, A a)
template<class Y> shared_ptr(const shared_ptr<Y>& r, T *p) noexcept;
shared_ptr(const shared_ptr& r) noexcept;
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
shared_ptr(shared_ptr&& r) noexcept;
template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept;
template<class Y> explicit shared_ptr(const weak_ptr<Y>& r);
template<class Y> shared_ptr(auto_ptr<Y>&& r);
template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
constexpr shared_ptr(nullptr_t) : shared_ptr() { }
Et comment vous voulez faire par exemple (pour unique_ptr)
pointer release() noexcept;
1 Postcondition: get () = = nullptr. 2 Retours: la valeur get () avait à le début de l'appel à la libération.
Sans ressource de pointeur? Vous essayez de pirater de la langue. C'est toujours une mauvaise idée.