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 comprendre comment unique_ptr
et shared_ptr
/ weak_ptr
fonctionne en général. Ce que je ne comprends pas, c'est l'usage réel. Il semble que tout le monde recommande de l'utiliser unique_ptr
presque tout le temps. Mais comment pourrais-je implémenter 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()
, non? 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
cela a plus de sens, sauf si j'utilise un pointeur dans une très petite portée?
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!
la source
device
au constructeur desettings
, voulez-vous pouvoir toujours y faire référence dans la portée appelante, ou uniquement viasettings
? Si ce dernier,unique_ptr
est utile. Vous avez également un scénario dans lequel la valeur de retour degetDevice()
estnull
. Sinon, renvoyez simplement une référence.shared_ptr
est correct dans 8/10 cas. Les 2/10 restants sont répartis entreunique_ptr
etweak_ptr
. En outre,weak_ptr
est généralement utilisé pour casser des références circulaires; Je ne suis pas sûr que votre utilisation soit considérée comme correcte.device
membre de données? Vous devez d'abord en décider.unique_ptr
place et abandonner la propriété lors de l'appel du constructeur, si je sais que je n'en aurai plus besoin pour le moment. Mais en tant que concepteur de laSettings
classe, je ne sais pas si l'appelant souhaite également conserver une référence. Peut-être que l'appareil sera utilisé dans de nombreux endroits. Ok, c'est peut-être exactement votre point. Dans ce cas, je ne serais pas le seul propriétaire et c'est à ce moment-là que j'utiliserais shared_ptr, je suppose. Et: les points intelligents remplacent donc les pointeurs, mais pas les références, non?Réponses:
Non pas forcément. L'important ici est de déterminer la politique de propriété appropriée pour votre
Device
objet, 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'
Settings
objet seul ? L'Device
objet devra-t-il être détruit automatiquement lorsque l'Settings
objet sera détruit, ou devrait-il survivre à cet objet?Dans le premier cas,
std::unique_ptr
c'est ce dont vous avez besoin, car il faitSettings
le seul (unique) propriétaire de l'objet pointé, et le seul objet qui est responsable de sa destruction.Sous cette hypothèse,
getDevice()
devrait retourner 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:[ NOTE 1: Vous vous demandez peut-être pourquoi j'utilise des pointeurs bruts ici, alors que tout le monde n'arrête pas de dire que les pointeurs bruts sont mauvais, dangereux et dangereux. En fait, c'est un avertissement précieux, mais il est important de le mettre dans le bon contexte: les pointeurs bruts sont mauvais lorsqu'ils sont utilisés pour effectuer une gestion manuelle de la mémoire , c'est-à-dire allouer et désallouer des objets via
new
etdelete
. Lorsqu'ils sont utilisés uniquement comme un moyen d'atteindre une sémantique de référence et de faire circuler des pointeurs d'observation non propriétaires, il n'y a rien d'intrinsèquement dangereux dans les pointeurs bruts, sauf peut-être le fait qu'il faut veiller à ne pas déréférencer un pointeur suspendu. - FIN NOTE 1 ][ NOTE 2: Comme il est apparu dans les commentaires, dans ce cas particulier où la propriété est unique et où l'objet possédé est toujours garanti d'être présent (c'est-à-dire que le membre de données interne
device
ne le sera jamaisnullptr
), la fonctiongetDevice()
pourrait (et devrait peut-être) renvoie 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 je voulais dire que c'était une réponse courte que l'on pourrait généraliser au cas où celadevice
pourrait êtrenullptr
, et pour montrer que les pointeurs bruts sont OK tant que l'on ne les utilise pas pour gestion manuelle de la mémoire. - FIN NOTE 2 ]La situation est bien sûr radicalement différente si votre
Settings
objet ne doit pas avoir la propriété exclusive de l'appareil. Cela pourrait être le cas, par exemple, si la destruction de l'Settings
objet ne devait pas également impliquer la destruction de l'Device
objet pointé .C'est quelque chose que seul vous, en tant que concepteur de votre programme, pouvez dire; d'après l'exemple que vous donnez, il m'est difficile de dire si c'est le cas ou non.
Pour vous aider à le comprendre, vous pouvez vous demander s'il existe d'autres objets en dehors de ceux
Settings
qui ont le droit de maintenir l'Device
objet en vie tant qu'ils tiennent un pointeur vers lui, au lieu d'être simplement des observateurs passifs. Si tel est effectivement le cas, vous avez besoin d'une politique de propriété partagée , quistd::shared_ptr
offre:Remarquez qu'il
weak_ptr
s'agit d'un pointeur d' observation , pas d'un pointeur propriétaire - en d'autres termes, il ne maintient pas l'objet pointé en vie si tous les autres pointeurs propriétaires vers l'objet pointé sont hors de portée.L'avantage par
weak_ptr
rapport à un pointeur brut ordinaire est que vous pouvez dire en toute sécurité s'ilweak_ptr
est suspendu ou non (c'est-à-dire s'il pointe vers un objet valide ou si l'objet pointé à l'origine a été détruit). Cela peut être fait en appelant laexpired()
fonction membre sur l'weak_ptr
objet.la source
weak_ptr
est toujours une alternative aux pointeurs d'observation bruts. Il est plus sûr dans un sens, car vous pouvez vérifier s'il est suspendu avant de le déréférencer, mais il comporte également des frais généraux. Si vous pouvez facilement garantir que vous n'allez pas déréférencer un pointeur suspendu, alors vous devriez être d'accord avec l'observation des pointeurs brutsgetDevice()
renvoyer une référence, n'est-ce pas? Ainsi, l'appelant n'aurait pas à vérifiernullptr
.auto myDevice = settings.getDevice()
va créer une nouvelle instance du typeDevice
appelémyDevice
et la copier-construire à partir de celle référencée par la référence quigetDevice()
retourne. Si vous voulezmyDevice
être une référence, vous devez le faireauto& myDevice = settings.getDevice()
. Donc, à moins que je ne manque quelque chose, nous sommes de retour dans la même situation que nous avions sans utiliserauto
.unique_ptr
à un client ouvre la possibilité que le client en quitte, acquérant ainsi la propriété et vous laissant avec un pointeur nul (unique).const_cast
s), personnellement, je ne le ferais pas. Il expose un détail de mise en œuvre, c'est-à-dire le fait que la propriété est unique et réalisée à travers ununique_ptr
. Je vois les choses de cette façon: si vous voulez / devez transmettre / retourner la propriété, passez / renvoyez un pointeur intelligent (unique_ptr
oushared_ptr
, selon le type de propriété). Si vous ne voulez / devez pas transmettre / retourner la propriété, utilisez unconst
pointeur ou une référence (correctement qualifié), principalement selon que l'argument peut être nul ou non.week_ptr
est utilisé uniquement pour les boucles de référence. Le graphe de dépendances doit être un graphe dirigé acyclique. Dans les pointeurs partagés, il existe 2 décomptes de références: 1 pourshared_ptr
s et 1 pour tous les pointeurs (shared_ptr
etweak_ptr
). Lorsque tous lesshared_ptr
s sont supprimés, le pointeur est supprimé. Lorsque le pointeur est nécessaire à partir deweak_ptr
,lock
doit être utilisé pour obtenir le pointeur, s'il existe.la source
shared_ptr
? Pouvez-vous expliquer pourquoi? Pour autant que je sache,weak_ptr
ne doit pas être compté car il crée simplement un nouveaushared_ptr
lors de l'opération sur l'objet (si l'objet sous-jacent existe toujours).lock
. La version boost est également thread-safe sur le comptage de références (delete
n'est appelée qu'une seule fois).weak_ptr::lock()
savoir si l'objet a expiré, il doit inspecter le "bloc de contrôle" qui contient le premier décompte de références et le pointeur vers l'objet, de sorte que le bloc de contrôle ne doit pas être détruit tant qu'il y a desweak_ptr
objets encore en cours d'utilisation, le nombre d'weak_ptr
objets doit donc être suivi, ce que fait le deuxième décompte de références. L'objet est détruit lorsque le premier nombre de références tombe à zéro, le bloc de contrôle est détruit lorsque le deuxième compte de références tombe à zéro.