Quel est le problème technique avec std::shared_ptr::unique()
qui est la raison de son obsolescence en C++17 ?
Selon cppreference.com, std::shared_ptr::unique()
est obsolète en C++17 car
cette fonction est obsolète depuis C++17 car
use_count
est seulement une approximation dans un environnement multi-thread.
Je comprends que c'est vrai pour use_count() > 1
: Tant que je tiens une référence, quelqu'un d'autre pourrait la lâcher simultanément ou en créer une nouvelle.
~~Mais si use_count()
retourne 1 (ce qui m'intéresse lorsque j'appelle unique()
) alors il n'y a pas d'autre thread qui puisse changer cette valeur de manière hasardeuse, donc je m'attends à ce que cela devrait être sûr :
if (myPtr && myPtr.unique()) {
//Modifier *myPtr
}~~
Résultats de ma propre recherche :
J'ai trouvé ce document : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0521r0.html qui propose l'obsolescence en réponse au commentaire CA 14 du CD de C++17, mais je n'ai pas pu trouver ledit commentaire lui-même.
En alternative, ce document propose d'ajouter quelques notes incluant ce qui suit :
Note : Lorsque plusieurs threads peuvent influencer la valeur de retour de
use_count()
, le résultat doit être traité comme une approximation. En particulier,use_count() == 1
n'implique pas que les accès via unshared_ptr
précédemment détruit ont été complétés d'une quelconque manière. — note de fin
Je comprends que cela pourrait être le cas pour la façon dont use_count()
est actuellement spécifié (en raison du manque de synchronisation garantie), mais pourquoi la résolution n'a-t-elle pas simplement été de spécifier une telle synchronisation et donc rendre le modèle ci-dessus sûr ? S'il y avait une limitation fondamentale qui empêcherait une telle synchronisation (ou la rendrait excessivement coûteuse), alors comment est-il possible de mettre en œuvre correctement le destructeur ?
Mise à jour :
J'ai négligé le cas évident présenté par @alexeykuzmin0 et @rubenvb, car jusqu'à présent j'avais seulement utilisé unique()
sur des instances de shared_ptr
qui n'étaient pas accessibles à d'autres threads eux-mêmes. Donc il n'y avait pas de danger que cette instance particulière soit copiée de manière hasardeuse.
Je serais toujours intéressé par ce que CA 14 était exactement, car je crois que tous mes cas d'utilisation pour unique()
fonctionneraient tant qu'il est garanti de se synchroniser avec ce qui arrive aux différentes instances de shared_ptr
sur d'autres threads. Cela me semble donc toujours être un outil utile, mais je pourrais passer à côté de quelque chose de fondamental ici.
Pour illustrer ce que j'ai en tête, considérez ce qui suit :
class MemoryCache {
public:
MemoryCache(size_t size)
: _cache(size)
{
for (auto& ptr : _cache) {
ptr = std::make_shared>();
}
}
// le morceau de mémoire retourné peut être transmis à un autre thread(s),
// mais la fonction n'est jamais accédée par deux threads simultanément
std::shared_ptr> getChunk()
{
auto it = std::find_if(_cache.begin(), _cache.end(), [](auto& ptr) { return ptr.unique(); });
if (it != _cache.end()) {
//la mémoire n'est plus utilisée par l'utilisateur précédent, elle peut donc être donnée à quelqu'un d'autre
return *it;
} else {
return{};
}
}
private:
std::vector>> _cache;
};
Y a-t-il quelque chose qui ne va pas (si unique()
se synchroniserait effectivement avec les destructeurs des autres copies) ?
1 votes
Pourquoi le 1 est-il un cas spécial ? Il pourrait y avoir une autre copie créée après votre appel à
unique
et avant que vous n'ayez fini ce que vous êtes en train de faire.0 votes
@rubenvb: Si
use_count == 1
, alors il n'y a - par définition - aucun autre thread ayant une référence à partir de laquelle il pourrait faire une copie.1 votes
@rubenvb: Mon erreur - unique est const, donc un autre thread pourrait en faire une copie sans qu'il s'agisse d'une course de données
1 votes
const
n'a rien à voir avec cela. Si un objet contenant le shared_ptr est lui-même accessible à partir de plusieurs threads, des copies du shared_ptr peuvent être faites.0 votes
@rubenvb : Si ce n'était pas const, alors vous auriez une course de données lorsque vous lisez en même temps à partir de la même instance de toute façon
0 votes
const
ne prévient pas les courses de données.0 votes
@rubenvb: Selon mes souvenirs, la bibliothèque standard garantit que tant que vous n'appelez que des méthodes const sur un objet, il n'y aura pas de conflits de données.
3 votes
@MikeMB, j'étais sur le point de répondre à votre mise à jour, mais j'ai vu qu'une phrase dans la réponse de alexeykuzmin0 répondait presque à votre mise à jour. Vous avez raison, si chacun de vos threads n'utilise que des copies d'un shared_ptr partagé, alors unique() fonctionnera bien. Dans le document que vous citez et dans la réponse de alexykuzmin0, plusieurs threads partagent une référence à un shared_ptr unique... Dans la réponse de alexeykuzmin0, la phrase "Le
unique()=true
signifie que personne n'a de shared_ptr pointant vers la même mémoire... peut dire presque la même chose, non?4 votes
Bien sûr,
use_count
n'a pas été déprécié, donc vous pouvez continuer à utiliseruse_count()==1
tant que vous vous souvenez que c'est risqué.