web-dev-qa-db-fra.com

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

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

Vous pouvez aussi utiliser unique_ptr<> pour établir une relation de propriété avec un objet ou une variable. Mais que se passe-t-il si d'autres objets non propriétaires souhaitent également référencer cet objet? weak_ptr<> n'est pas utile dans ce cas. Les pointeurs bruts sont utiles mais apportent divers inconvénients (par exemple, ils peuvent être initialisés automatiquement à nullptr mais cela est accompli grâce à des techniques qui ne sont pas cohérentes avec le std::*_ptr<> les 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 de clarification 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 sans propriétaire avec un objet appartenant via unique_ptr<> autre part. Mais le brut est-il le mieux que nous puissions faire?

L'espoir est un type de pointeur qui:

  • 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 tous les pointeurs via une recherche de _ptr< (grossièrement).
  • Initialise automatiquement à nullptr.

Donc:

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

Ce type existe-t-il déjà en C++ maintenant, est-il proposé pour l'avenir, ou est-ce qu'une autre implémentation est largement disponible dans par ex. Renforcer?

54
OldPeculier

Il existe un réel besoin pour un type de pointeur standard d'agir comme un contrepoint non propriétaire, peu coûteux et bien élevé de std::unique_ptr<>. Un tel pointeur n'a pas encore été standardisé, mais un standard a été proposé et est en cours de discussion par le comité des normes C++. Le "pointeur intelligent le plus stupide du monde", alias std::exempt_ptr<> aurait la sémantique générale des autres classes de pointeurs C++ modernes mais n'aurait aucune responsabilité non plus de posséder l'objet pointé (comme shared_ptr et unique_ptr do) ou pour avoir répondu correctement à la suppression de cet objet (comme weak_ptr Est-ce que).

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

21
OldPeculier

Le comportement "notifier" de shared_ptr Nécessite le comptage de référence du bloc de contrôle de comptage de référence. Les blocs de contrôle de comptage de référence de shared_ptr Utilisent pour cela des comptages de référence séparés. Les instances de weak_ptr Conservent des références à ce bloc, et weak_ptr Elles-mêmes empêchent le bloc de contrôle du nombre de références d'être delete édité. L'objet pointé a son destructeur appelé lorsque le nombre fort passe à zéro (ce qui peut ou non entraîner l'ion delete de la mémoire où cet objet a été stocké), et le bloc de contrôle est delete ed uniquement lorsque le nombre de références faibles atteint zéro.

Le principe de unique_ptr Est qu'il n'a aucun frais généraux sur un pointeur ordinaire. L'allocation et la maintenance des blocs de contrôle du nombre de références (pour prendre en charge weak_ptr - sémantique ish) rompt ce principe. Si vous avez besoin d'un comportement de cette description, vous voulez vraiment une sémantique partagée, même si d'autres références à l'objet ne sont pas propriétaires. Il y a toujours du partage dans ce cas - le partage de l'état de la destruction ou non de l'objet.

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


ÉDITER:

Dans le cas de votre exemple, il semble que Victim devrait demander un Trebuchet& Plutôt qu'un Trebuchet*. Il est alors clair à 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() );
}
38
Billy ONeal

L'analogue non dû de unique_ptr Est un simple pointeur C. Ce qui est différent - le pointeur C ne sait pas si les données pointées sont toujours accessibles. weak_ptr D'autre part. Mais il est impossible de remplacer le pointeur raw par un pointeur connaissant la validité des données sans surcharge supplémentaire (et weak_ptr A cette surcharge). Cela signifie que le pointeur de style C est le meilleur en termes de vitesse que vous pouvez obtenir en tant qu'analogue non dû pour unique_ptr.

7
sasha.sochka

Bien que vous ne puissiez pas obtenir gratuitement un pointeur "faible" vers un objet appartenant uniquement, le concept est utile et est utilisé dans quelques systèmes. Voir WeakPtr de Chromium et QPointer de QT pour les implémentations.

Le WeakPtr de Chromium est implémenté de manière intrusive en stockant un shared_ptr à l'intérieur de l'objet faiblement référençable et en le marquant invalide lorsque l'objet est détruit. WeakPtrs référence ensuite ce ControlBlock et vérifie s'il est valide avant de distribuer son pointeur brut. Je suppose que le QPointer de QT est implémenté de la même manière. Parce que la propriété n'est pas partagée, l'objet d'origine est détruit de façon déterministe.

Cependant, cela signifie que le déréférencement du WeakUniquePtr n'est pas thread-safe:

Fil 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 produit en même temps que la ligne B, le thread 2 se terminera à l'aide d'un pointeur suspendu. std::weak_ptr éviterait ce problème en en prenant atomiquement une référence propriétaire partagée à l'objet avant de laisser le thread 2 l'utiliser , mais cela viole l'hypothèse ci-dessus que l'objet est la propriété 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 message sur le même thread. (Notez qu'il est toujours parfaitement sûr de copier le WeakUniquePtr dans les deux sens avant de l'utiliser.)

On pourrait imaginer utiliser un suppresseur personnalisé dans std::unique_ptr pour implémenter cela en utilisant des types de bibliothèques standard, mais cela reste un exercice pour le lecteur.

6
Jeffrey Yasskin
boost::optional<Trebuchet&>

Comme l'a souligné Billy ONeal 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 :: optionnel <T &> vs T *

Remarque: std::optional<T> est en voie de le faire en C++ 14 mais std::optional<T&> est une proposition distincte qui n'est pas dans le projet C++ 14 actuel. Plus de détails ici: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html

3
ChetS

otn::raw::weak (de Bibliothèque de jetons d'objets C++ ) est un contrepoint non propriétaire, peu coûteux et bien élevé à std::unique_ptr. Toujours dans la bibliothèque, il y a otn::safe::unique, un propriétaire unique qui peut "notifier" un non propriétaire otn::safe::weak sur la suppression de l'objet.

#include <otn/all.hpp>
#include <iostream>

int main()
{
    using namespace std;
    using namespace otn;

    raw::weak_optional<int> raw_weak;
    if (!raw_weak)
        cout << "raw_weak is empty" << endl;

    cout << "--- create object in std_unique..." << endl;
    auto std_unique = std::make_unique<int>(42);
    raw_weak = std_unique;
    if (std_unique)
        cout << "std_unique is not empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty" << endl;

    cout << "--- move std_unique to safe_unique..." << endl;
    safe::unique_optional<int> safe_unique = std::move(std_unique);

    if (!std_unique)
        cout << "std_unique is empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is observs safe_unique" << endl;

    safe::weak_optional<int> safe_weak = safe_unique;
    if (safe_unique)
        cout << "safe_unique is not empty" << endl;
    if (!safe_weak.expired())
        cout << "safe_weak is not expired" << endl;

    cout << "--- destroy object in safe_unique..." << endl;
    utilize(std::move(safe_unique));
    if (!safe_unique)
        cout << "safe_unique is empty" << endl;
    if (safe_weak.expired())
        cout << "safe_weak is expired, it is not dangling" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is dangling!!!" << endl;
}

Production:

raw_weak is empty
--- create object in std_unique...
std_unique is not empty
raw_weak is not empty
--- move std_unique to safe_unique...
std_unique is empty
raw_weak is not empty, it is observs safe_unique
safe_unique is not empty
safe_weak is not expired
--- destroy object in safe_unique...
safe_unique is empty
safe_weak is expired, it is not dangling
raw_weak is not empty, it is dangling!!!
1
ViTech

Dans le nouveau monde C++ avec shared_ptr, faible_ptr et unique_ptr, vous ne devez pas stocker de références à des objets de longue durée, comme votre trébuchet, à l'aide de pointeurs ou de références bruts. Au lieu de cela, World devrait avoir un shared_ptr sur le trébuchet et Victim devrait stocker un shared_ptr ou un faiblesse_ptr, selon que le trébuchet devrait rester avec la victime si le monde disparaît. L'utilisation d'un faiblesse_ptr vous permet de dire si le pointeur est toujours valide (c'est-à-dire que le monde existe toujours), il n'y a aucun moyen de le faire avec un pointeur brut ou une référence.

Lorsque vous utilisez un unique_ptr, vous déclarez que seule l'instance World possédera le trébuchet. Les clients de la classe World peuvent utiliser le trébuchet de l'objet World en appelant la méthode "get" mais ne doivent pas conserver la référence ou le pointeur renvoyé par la méthode lorsqu'ils ont fini de l'utiliser. Au lieu de cela, ils devraient "emprunter" le trébuchet chaque fois qu'ils veulent l'utiliser en appelant la méthode "get".

Cela étant dit, il peut y avoir des cas où vous souhaitez stocker une référence ou un pointeur brut pour une utilisation future afin d'éviter la surcharge du shared_ptr. Mais ces instances sont rares et vous devez être sûr que vous n'utiliserez pas le pointeur ou la référence après que l'objet World qui possède le trébuchet soit parti.

1
Brett Hall

Une fonction qui prend un pointeur ou une référence brute promet implicitement de ne pas conserver une copie de ce pointeur après le retour de la fonction. En retour, l'appelant promet que le pointeur est valide (ou nullptr) jusqu'à ce que l'appelé soit revenu.

Si vous souhaitez conserver le pointeur, vous le partagez (et devez utiliser shared_ptr). UNE unique_ptr gère une copie nique du pointeur. Vous utilisez des pointeurs (ou références) bruts pour faire référence aux fonctions d'appel impliquant cet objet.

C'est la même chose pour shared_ptr objets. weak_ptr n'entre en jeu que lorsque vous voulez avoir une référence supplémentaire à l'objet pointé trop qui survit à la fonction impliquée. Le principal objectif de faiblesse_ptr est de briser les cycles de référence où deux objets contiennent des références l'un à l'autre (et ne sont donc jamais libérés).

Rappelez-vous cependant que prendre shared_ptr ou weak_ptr implique que la fonction qui prend ce paramètre modifiera (éventuellement) un autre objet pour conserver une référence à l'objet pointé qui survit à l'invocation de la fonction. Dans la grande majorité des cas, vous utilisez un pointeur brut (si nullptr est une valeur valide) ou ref (lorsqu'une valeur est garantie) même pour shared_ptr ou faiblesse_ptr.

0
Paul de Vrieze