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!
Un
unique_ptr
ne fonctionnerait pas à cause degetDevice()
, 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
.
_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.