123 votes

Pourquoi est shared_ptr <void> légal, alors que unique_ptr <void> est mal formé?

La question se situe vraiment dans le titre: je suis curieux de savoir quelle est la raison technique de cette différence, mais aussi la raison?

 std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
 

147voto

Nawaz Points 148870

C'est parce qu' std::shared_ptr implémente type-erasure, tout en std::unique_ptr ne le sont pas.


Depuis std::shared_ptr implémente type-erasure, il prend également en charge une autre propriété intéressante, viz. il n'a pas besoin du type de la deleter comme le modèle type de l'argument de la classe de modèle. 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 pas l'avoir.

Maintenant, la question est, pourquoi est - shared_ptr mettre en œuvre type-erasure? Eh bien, il le fait, car il a le soutien de comptage de références, et à l'appui de cela, il a d'allouer de la mémoire de tas et depuis il a pour allouer de la mémoire de toute façon, il va plus loin et met en œuvre des type-erasure — qui a besoin d'allocation de tas trop. Donc, fondamentalement, c'est juste d'être opportuniste!

En raison de l'effacement, std::shared_ptr est capable de supporter deux choses:

  • Il peut stocker des objets de tout type void*, mais il est encore capable de supprimer les objets sur la destruction correctement en invoquant leur destructeur.
  • Le type de deleter est pas passé comme argument de type de la classe de modèle, ce qui signifie un peu de liberté sans compromettre la sécurité du type.

Alright. Qui est tout au sujet de combien de std::shared_ptr travaux.

Maintenant, la question est, pouvez - std::unique_ptr stocker des objets comme void*? Eh bien, la réponse est oui , à condition que vous passer un adapté deleter comme argument. Voici une de ces manifestations:

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 ;-)

De 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'ai besoin d'un type d'effacement deleter, mais il semble possible (au prix de quelques tas de répartition). En gros, est-ce à dire qu'il n'y est en fait une niche place pour un 3ème type de pointeur intelligent: une propriété exclusive de pointeur intelligent avec le type d'effacement.

pour ce qui @Steve Jessop a suggéré la solution suivante,

Je ne l'ai jamais essayé, mais peut-être que vous pourriez réaliser que par un std::function comme le deleter type unique_ptr? Supposant que cela fonctionne réellement, alors vous êtes fait, la propriété exclusive et un type effacé deleter.

Suite à cette suggestion, je l'ai fait (bien qu'il ne fait pas usage de l' std::function comme il 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"));
}  

De 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.

Espérons que cela aide.

7voto

Richard Hodges Points 1972

L'une des justifications est dans l'un des nombreux cas d'utilisation d'un shared_ptr - à savoir une durée de vie de l'indicateur ou de la sentinelle.

Cela a été mentionné dans l'original stimuler la documentation:

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

closure_target est quelque chose comme ceci:

struct closure_target {
    std::function<void()> closure;
    std::weak_ptr<void> sentinel;
};

L'appelant inscrire un rappel de 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 
    }
};

parce qu' shared_ptr<X> est toujours convertibles shared_ptr<void>, le event_emitter peut maintenant être au courant du type d'objet, il est de retour d'appel.

Cet arrangement communiqués abonnés à l'événement de l'émetteur de l'obligation de la manipulation de la traversée des cas (si la fonction de rappel dans une file d'attente, en attente d'être traitées tout en active_object s'en va?), et aussi signifie qu'il n'est pas nécessaire de synchroniser le désabonnement. weak_ptr<void>::lock est un mode synchrone.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X