56 votes

shared_ptr<> est à weak_ptr<> comme unique_ptr<> est à... quoi ?

En C++11, vous pouvez utiliser un shared_ptr<> d'établir une relation de propriété avec un objet ou une variable et weak_ptr<> pour référencer en toute sécurité cet objet de manière non propriétaire.

Vous pouvez également utiliser unique_ptr<> établir une relation de propriété avec un objet ou une variable. Mais qu'en est-il si d'autres objets, non propriétaires, veulent également faire référence à cet objet ? weak_ptr<> n'est pas utile dans ce cas. Les pointeurs bruts sont utiles mais présentent divers inconvénients (par exemple, ils peuvent être automatiquement initialisé à nullptr mais cela se fait par le biais de techniques qui ne sont pas cohérentes avec la std::*_ptr<> types).

Quel est l'équivalent de weak_ptr<> pour les références non propriétaires à des objets possédés via unique_ptr<> ?

Voici un exemple éclairant qui ressemble à quelque chose dans un jeu sur lequel je travaille.

class World
{
public:

    Trebuchet* trebuchet() const { return m_trebuchet.get(); }

private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};

class Victim
{
public:
    Victim( Trebuchet* theTrebuchet ) : m_trebuchet( theTrebuchet ) {}

    ~Victim()
    {
        delete m_trebuchet;     // Duh. Oops. Dumb error. Nice if the compiler helped prevent this.
    }

private:

    Trebuchet* m_trebuchet;    // Non-owning.
};

shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}

Ici, nous utilisons un pointeur brut pour maintenir une relation de non possession avec un objet possédé par l'intermédiaire de unique_ptr<> ailleurs. Mais le brut est-il le mieux que nous puissions faire ?

L'espoir est un type de pointeur qui :

  • Il ressemble aux autres types de pointeurs modernes. Par exemple std::raw_ptr<T> .
  • Remplace les pointeurs bruts afin qu'une base de code qui utilise des types de pointeurs modernes partout puisse trouver todo via une recherche de _ptr< (en gros).
  • Auto-initialise à nullptr.

Ainsi :

int* p;                  // Unknown value.
std::raw_ptr< int > p;   // null.

Ce type existe-t-il déjà en C++, est-il proposé pour l'avenir, ou une autre implémentation est-elle largement disponible dans Boost, par exemple ?

11 votes

unique_ptr::get si vous voulez accéder au pointeur sous-jacent. Il n'y a pas de weak_ptr équivalent car alors le unique_ptr ne serait pas très unique

6 votes

Il n'y a rien de tel parce que unique_ptr est conçu pour ne pas avoir de surcharge par rapport à un pointeur brut. S'il devait garder un refcount de tous les pointeurs faibles, cela ne serait pas possible.

1 votes

@Xeo, c'est juste. A weak_ptr équivalent pour unique_ptr pourrait avoir un coût excessif. Mais ne pourrait-il pas y avoir une sorte de fonctionnalité intermédiaire : un type de pointeur syntaxiquement similaire aux autres types de pointeurs modernes, avec annulation automatique à la construction, prévention des appels directs à la suppression, etc. Ces types de pointeurs seraient vraisemblablement toujours sujets aux problèmes de pointeurs pendants, mais ils ne seraient pas pires - et même meilleurs - que les pointeurs bruts. Un tel type existe-t-il ?

38voto

Billy ONeal Points 50631

Le comportement "notify" de shared_ptr nécessite un comptage de référence le bloc de contrôle du comptage de référence. shared_ptr Le(s) bloc(s) de contrôle du comptage de référence de l'entreprise utilise(nt) des comptages de référence séparés pour cela. weak_ptr maintiennent des références à ce bloc, et weak_ptr empêchent eux-mêmes le bloc de contrôle de comptage de référence d'être delete ed. Le destructeur de l'objet pointé est appelé lorsque le nombre d'objets forts atteint zéro (ce qui peut ou non entraîner la création de delete ion de la mémoire où cet objet a été stocké), et le bloc de contrôle est delete uniquement lorsque le nombre de références faibles passe à zéro.

unique_ptr Le principe de l'algorithme est qu'il n'y a pas de surcharge par rapport à un simple pointeur. L'allocation et la maintenance des blocs de contrôle de comptage de références (pour supporter les blocs de contrôle de comptage de références) est une tâche difficile. weak_ptr -sémantique) brise ce principe. Si vous avez besoin d'un comportement de cette description, alors vous voulez vraiment une sémantique partagée, même si les autres références à l'objet ne sont pas propriétaires. Il y a toujours un partage dans ce cas -- le partage de l'état de savoir si l'objet a été détruit ou non.

Si vous avez besoin d'une référence générique de non-propriété et que vous n'avez pas besoin de notification, utilisez des pointeurs ou des références simples à l'élément dans le dossier de l'utilisateur. unique_ptr .


EDIT :

Dans le cas de votre exemple, on dirait que Victim devrait demander un Trebuchet& plutôt qu'un Trebuchet* . On sait alors clairement à qui appartient l'objet en question.

class World
{
public:

    Trebuchet& trebuchet() const { return *m_trebuchet.get(); }

private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};

class Victim
{
public:
    Victim( Trebuchet& theTrebuchet ) : m_trebuchet( theTrebuchet ) {}

    ~Victim()
    {
        delete m_trebuchet;     // Compiler error. :)
    }

private:

    Trebuchet& m_trebuchet;    // Non-owning.
};

shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}

0 votes

@Potatoswatter : Merci. Corrigé !

1 votes

L'initialisation d'une référence dans le constructeur est juste compte tenu de mon exemple, mais mon exemple ne permet pas cette solution de contournement. Le cas le plus général (et le cas que j'ai effectivement traité dans diverses circonstances dans le code) permet au pointeur d'être défini après la construction.

1 votes

@OldPeculier : J'ai donné la "réponse générale" dans la première partie de ma réponse, et la réponse spécifique dans la deuxième partie.

23voto

OldPeculier Points 1972

Il y a un réel besoin d'un type de pointeur standard pour agir comme un contrepoint non propriétaire, peu coûteux et bien élevé aux std::unique_ptr<> . Aucun pointeur de ce type n'a encore été normalisé, mais un Une norme a été proposée et est en cours de discussion au sein du comité des normes C++. Le "Pointeur intelligent le plus bête du monde", alias std::exempt_ptr<> aurait la sémantique générale des autres classes de pointeurs modernes du C++, mais ne serait pas responsable de la possession de l'objet pointé (comme le sont les classes de pointeurs de l'UE). shared_ptr y unique_ptr faire) ou pour répondre correctement à la suppression de cet objet (comme weak_ptr fait).

En supposant que cette fonctionnalité soit finalement ratifiée par le comité, elle répondrait pleinement au besoin souligné dans cette question. Même si elle n'est pas ratifiée par le comité, le document ci-dessus exprime pleinement le besoin et décrit une solution complète.

2 votes

Il convient de noter que la proposition de exempt_ptr ne vous donne pas la sémantique de notification que vous demandiez. Il n'y a pas de pointeur standard qui fait cela, mais il est trivial d'en écrire un.

1 votes

@BillyONeal Veuillez comprendre : Je n'ai jamais demandé de sémantique de notification. J'ai laissé entendre que ce serait bien, mais ce n'est pas intrinsèque au problème.

1 votes

La version 3 de la proposition est disponible ici : http://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3840.pdf exempt_ptr a été renommé en observer_ptr

8voto

sasha.sochka Points 4937

unique_ptr L'analogie de la non-voyante est un pointeur C ordinaire. Ce qui est différent - le pointeur C ne sait pas si les données pointées sont toujours accessibles. weak_ptr en revanche, le fait. Mais il est impossible de remplacer raw avec un pointeur connaissant la validité des données sans surcharge supplémentaire (et weak_ptr a cette surcharge). Cela implique que le pointeur de style C est le meilleur en termes de vitesse que l'on puisse obtenir en tant qu'analogue non surchargé de unique_ptr .

0 votes

Pas tout à fait, proche, mais pas tout à fait.

4 votes

Dans le monde du C++, un pointeur C ordinaire est un pointeur "je ne vais pas vous parler de propriété".

7voto

Jeffrey Yasskin Points 1472

Bien que vous ne puissiez pas obtenir gratuitement un pointeur "faible" vers un objet à propriété unique, le concept est utile et est utilisé dans quelques systèmes. Voir WeakPtr de Chromium y Le QPointer de QT pour les mises en œuvre.

Le WeakPtr de Chromium est implémenté de manière intrusive en stockant un shared_ptr à l'intérieur de l'objet à référence faible et en le marquant invalide lorsque l'objet est détruit. Les WeakPtrs référencent alors ce ControlBlock et vérifient s'il est valide avant de distribuer leur pointeur brut. Je suppose que le QPointer de QT est implémenté de manière similaire. Comme la propriété n'est pas partagée, l'objet original est détruit de façon déterministe.

Cependant cela signifie que le déréférencement de l WeakUniquePtr n'est pas sécurisé :

Fil conducteur 1 :

unique_ptr<MyObject> obj(new MyObject);
thread2.send(obj->AsWeakPtr());
...
obj.reset();  // A

Thread2 :

void receive(WeakUniquePtr<MyObject> weak_obj) {
  if (MyObject* obj = weak_obj.get()) {
    // B
    obj->use();
  }
}

Si la ligne A se déroule en même temps que la ligne B le fil 2 se retrouvera avec un pointeur qui pend. std::weak_ptr permettrait d'éviter ce problème en prendre atomiquement une référence de propriété partagée à l'objet avant de laisser le fil 2 l'utiliser mais cela viole l'hypothèse ci-dessus que l'objet est possédé de manière unique. Cela signifie que toute utilisation d'un WeakUniquePtr doit être synchronisée avec la destruction de l'objet réel, et la façon la plus simple de le faire est d'exiger qu'elles soient effectuées dans une boucle de messages sur le même thread. (Notez qu'il est toujours tout à fait sûr de copier l'objet WeakUniquePtr d'un fil à l'autre avant de l'utiliser).

On pourrait imaginer utiliser un suppresseur personnalisé dans std::unique_ptr d'implémenter ceci en utilisant les types de la bibliothèque standard, mais nous laissons cela comme un exercice pour le lecteur.

3voto

ChetS Points 328
boost::optional<Trebuchet&>

Comme Billy ONeal l'a souligné dans sa réponse, vous voudrez probablement passer un Trebuchet& au lieu d'un pointeur. Le problème avec la référence est que vous ne pouvez pas passer un nullptr , boost::optional fournit un moyen d'avoir l'équivalent d'un nullptr . Plus de détails sur boost::optional sont ici : http://www.boost.org/doc/libs/1_54_0/libs/optional/doc/html/boost_optional/detailed_semantics.html

Voir aussi cette question : boost::optional<T&> vs T*

Note : std::optional<T> est en passe d'être intégré dans C++14 mais std::optional<T&> est une proposition distincte qui ne figure pas dans le projet actuel de C++14. Plus de détails ici : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html

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