web-dev-qa-db-fra.com

Utilisation de pointeurs intelligents pour les membres de la classe

J'ai du mal à comprendre l'utilisation des pointeurs intelligents en tant que membres de classe dans C++ 11. J'ai beaucoup lu sur les pointeurs intelligents et je pense bien comprendre le fonctionnement de unique_ptr et shared_ptr/weak_ptr en général. Ce que je ne comprends pas, c’est l’usage réel. Il semble que tout le monde recommande d'utiliser unique_ptr comme moyen d'aller presque tout le temps. Mais comment pourrais-je mettre en œuvre quelque chose comme ceci:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

Disons que je voudrais remplacer les pointeurs par des pointeurs intelligents. Un unique_ptr ne fonctionnerait pas à cause de getDevice(), n'est-ce pas? C’est donc le moment où j’utilise shared_ptr et weak_ptr? Pas moyen d'utiliser unique_ptr? Il me semble que dans la plupart des cas, shared_ptr a plus de sens, sauf si j'utilise un pointeur dans un très petit champ?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

Est-ce la voie à suivre? Merci beaucoup!

143
michaelk

Un unique_ptr ne fonctionnerait pas à cause de getDevice(), n'est-ce pas?

Non pas forcément. Ce qui est important ici est de déterminer le politique de propriété approprié pour votre objet Device, c’est-à-dire qui sera le propriétaire de l’objet pointé par votre pointeur (intelligent).

Est-ce que ce sera l'instance de l'objet Settings seul ? L'objet Device devra-t-il être détruit automatiquement lorsque l'objet Settings sera détruit, ou devra-t-il survivre à cet objet?

Dans le premier cas, std::unique_ptr est ce dont vous avez besoin, car il fait de Settings le seul (unique) propriétaire de l'objet pointé, et le seul objet responsable de sa destruction.

Sous cette hypothèse, getDevice() devrait renvoyer un simple pointeur d'observation (les pointeurs d'observation sont des pointeurs qui ne maintiennent pas l'objet pointé en vie). Le type le plus simple de pointeur d'observation est un pointeur brut:

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[NOTE 1: Vous vous demandez peut-être pourquoi j'utilise des pointeurs bruts ici, alors que tout le monde continue de dire que les pointeurs bruts sont mauvais, dangereux et sans danger. En réalité, il s'agit d'un avertissement précieux, mais il est important de le placer dans le bon contexte: les pointeurs bruts sont incorrects lorsqu'ils sont utilisés pour la gestion manuelle de la mémoire, c'est-à-dire l'allocation et la désaffectation d'objets par new et delete. Utilisé purement comme moyen de réaliser une sémantique de référence et de contourner les pointeurs non propriétaires, il n’ya rien de intrinsèquement dangereux dans les pointeurs bruts, si ce n’est peut-être le fait qu’il faut prendre soin de ne pas déréférencer un pointeur pendant. - FIN NOTE 1]

[NOTE 2: Comme il est apparu dans les commentaires, dans ce cas particulier où la propriété est unique et l'objet détenu est toujours garanti d'être present (c'est-à-dire que le membre de données interne device ne sera jamais nullptr), la fonction getDevice() pourrait (et devrait peut-être) renvoyer une référence plutôt qu'un pointeur. Bien que cela soit vrai, j’ai décidé de renvoyer un pointeur brut ici parce que j’entendais être une réponse courte que l’on pourrait généraliser au cas où device pourrait être nullptr et montrer que les pointeurs bruts sont OK tant que l’on ne les utilise pas pour la gestion manuelle de la mémoire. - FIN NOTE 2]


Bien entendu, la situation est radicalement différente si votre objet Settings doit pas posséder la propriété exclusive du périphérique. Ce pourrait être le cas, par exemple, si la destruction de l'objet Settings n'implique pas également la destruction de l'objet Device pointu.

C’est quelque chose que seul vous, concepteur de votre programme, pouvez dire; D'après votre exemple, il m'est difficile de dire si c'est le cas ou non.

Pour vous aider à comprendre, vous pouvez vous demander s’il existe d'autres objets que Settings qui sont autorisés à conserver l’objet Device aussi longtemps qu’ils sont dotés d’un pointeur. juste des observateurs passifs. Si tel est effectivement le cas, vous avez besoin d'une règle de propriété partagée, qui correspond à ce que std::shared_ptr propose:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

Notez que weak_ptr est un pointeur observant et non un pointeur propriétaire. En d’autres termes, il ne garde pas l’objet en vie si tous les autres pointeurs propriétaires du pointeur objet sortir de la portée.

L’avantage de weak_ptr par rapport à un pointeur brut classique est qu’il est possible de dire en toute sécurité si weak_ptr est en suspension ou non (c.-à-d. S’il pointe vers un objet, ou si l'objet initialement désigné a été détruit). Cela peut être fait en appelant la fonction membre expired() sur l'objet weak_ptr.

188
Andy Prowl
_class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}
_

week_ptr est utilisé uniquement pour les boucles de référence. Le graphe de dépendance doit être un graphe à direction acyclique. Dans les pointeurs partagés, il existe 2 comptes de référence: 1 pour shared_ptr s, et 1 pour tous les pointeurs (_shared_ptr_ et _weak_ptr_). Lorsque tous les _shared_ptr_ s sont supprimés, le pointeur est supprimé. Lorsqu'un pointeur est nécessaire à partir de _weak_ptr_, lock doit être utilisé pour obtenir le pointeur, s'il existe.

0
Naszta