Réponses:
C'est parce que std::shared_ptr
met en œuvre l'effacement de type, alors que ce std::unique_ptr
n'est pas le cas.
Depuis std::shared_ptr
implémente l'effacement de type, il prend également en charge une autre propriété intéressante, à savoir. il n'a pas besoin du type du suppresseur comme argument de type de modèle pour le modèle de classe. Regardez leurs déclarations:
template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;
qui a Deleter
comme paramètre de type, tandis que
template<class T>
class shared_ptr;
ne l'a pas.
Maintenant, la question est, pourquoi shared_ptr
implémente l'effacement de type? Eh bien, il le fait, car il doit prendre en charge le comptage de références, et pour prendre en charge cela, il doit allouer de la mémoire à partir du tas et comme il doit allouer de la mémoire de toute façon, il va plus loin et implémente l'effacement de type - qui a besoin de tas allocation aussi. Donc, fondamentalement, il s'agit simplement d'être opportuniste!
En raison de l'effacement de type, std::shared_ptr
est capable de prendre en charge deux choses:
void*
, mais il est toujours capable de supprimer correctement les objets lors de la destruction en invoquant correctement leur destructeur .Bien. Tout dépend de la façon dont cela std::shared_ptr
fonctionne.
Maintenant, la question est, peut std::unique_ptr
stocker des objets comme void*
? Eh bien, la réponse est oui - à condition que vous passiez un suppresseur approprié comme argument. Voici une telle démonstration:
int main()
{
auto deleter = [](void const * data ) {
int const * p = static_cast<int const*>(data);
std::cout << *p << " located at " << p << " is being deleted";
delete p;
};
std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter);
} //p will be deleted here, both p ;-)
Sortie ( démo en ligne ):
959 located at 0x18aec20 is being deleted
Vous avez posé une question très intéressante dans le commentaire:
Dans mon cas, j'aurai besoin d'un suppresseur d'effacement de type, mais cela semble également possible (au prix d'une allocation de tas). Fondamentalement, cela signifie-t-il qu'il existe en fait une niche pour un troisième type de pointeur intelligent: un pointeur intelligent de propriété exclusive avec effacement de type.
auquel @Steve Jessop a suggéré la solution suivante,
Je n'ai jamais vraiment essayé cela, mais vous pourriez peut-être y parvenir en utilisant un type approprié
std::function
comme suppression avecunique_ptr
? En supposant que cela fonctionne réellement, vous avez terminé, la propriété exclusive et un suppresseur de type effacé.
Suite à cette suggestion, j'ai implémenté ceci (bien que cela ne l'utilise pas std::function
car cela ne semble pas nécessaire):
using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;
template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
return unique_void_ptr(ptr, [](void const * data) {
T const * p = static_cast<T const*>(data);
std::cout << "{" << *p << "} located at [" << p << "] is being deleted.\n";
delete p;
});
}
int main()
{
auto p1 = unique_void(new int(959));
auto p2 = unique_void(new double(595.5));
auto p3 = unique_void(new std::string("Hello World"));
}
Sortie ( démo en ligne ):
{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.
J'espère que cela pourra aider.
std::function
type de suppression approprié avec unique_ptr
? En supposant que cela fonctionne réellement, vous avez terminé, la propriété exclusive et un suppresseur de type effacé.
L'une des justifications se trouve dans l'un des nombreux cas d'utilisation de a shared_ptr
- à savoir comme indicateur de durée de vie ou sentinelle.
Cela a été mentionné dans la documentation originale du boost:
auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv)
{
auto closure_target = { closure, std::weak_ptr<void>(pv) };
...
// store the target somewhere, and later....
}
void call_closure(closure_target target)
{
// test whether target of the closure still exists
auto lock = target.sentinel.lock();
if (lock) {
// if so, call the closure
target.closure();
}
}
Où closure_target
est quelque chose comme ça:
struct closure_target {
std::function<void()> closure;
std::weak_ptr<void> sentinel;
};
L'appelant enregistrerait un rappel quelque chose comme ceci:
struct active_object : std::enable_shared_from_this<active_object>
{
void start() {
event_emitter_.register_callback([this] { this->on_callback(); },
shared_from_this());
}
void on_callback()
{
// this is only ever called if we still exist
}
};
car shared_ptr<X>
est toujours convertible en shared_ptr<void>
, le event_emitter peut maintenant être parfaitement inconscient du type d'objet dans lequel il rappelle.
Cette disposition libère les abonnés de l'émetteur d'événements de l'obligation de gérer les cas de croisement (que se passe-t-il si le rappel est dans une file d'attente, en attente d'être actionné pendant que active_object disparaît?), Et signifie également qu'il n'est pas nécessaire de synchroniser la désinscription. weak_ptr<void>::lock
est une opération synchronisée.
std::unique_ptr<void, D>
est toujours possible en fournissant un fichier appropriéD
.