Quel est le problème technique avec std::shared_ptr::unique()
qui est la raison de sa dépréciation en C++17?
Selon cppreference.com, std::shared_ptr::unique()
est déprécié en C++17 car
cette fonction est dépréciée à partir de C++17 car
use_count
est seulement une approximation dans un environnement multi-thread.
Je comprends que cela est vrai pour use_count() > 1
: Pendant que je détiens une référence, quelqu'un d'autre peut simultanément la lâcher ou en créer une nouvelle
~~Mais si use_count()
renvoie 1 (ce qui m'intéresse lorsque j'appelle unique()
) alors il n'y a pas d'autre thread qui pourrait changer cette valeur de manière aléatoire, donc je m'attendrais à ce que cela soit 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 la dépréciation en réponse au commentaire CA 14 du CD 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 affecter la valeur renvoyée par
use_count()
, le résultat doit être traité comme une approximation. En particulier,use_count() == 1
n'implique pas que les accès à travers unshared_ptr
précédemment détruit ont été complétés de quelque manière que ce soit. — fin note
Je comprends que cela pourrait être le cas pour la manière dont use_count()
est actuellement spécifié (en raison du manque de synchronisation garantie), mais pourquoi la résolution n'a-t-elle pas été simplement de spécifier une telle synchronisation et ainsi rendre le motif ci-dessus sûr? S'il y avait une limitation fondamentale qui n'autoriserait pas une telle synchronisation (ou la rendrait prohibitivement coûteuse), comment est-il possible d'implémenter correctement le destructeur?
Mise à jour:
J'ai négligé le cas évident présenté par @alexeykuzmin0 et @rubenvb, car jusqu'à présent j'ai seulement utilisé unique()
sur des instances de shared_ptr
qui n'étaient pas accessibles à d'autres threads eux-mêmes. Ainsi, il n'y avait aucun danger que cette instance particulière soit copiée de manière aléatoire.
Je serais toujours intéressé d'entendre ce que CA 14 était précisément, car je crois que tous mes cas d'utilisation de unique()
fonctionneraient tant qu'il est garanti de se synchroniser avec ce qui se passe avec différentes instances de shared_ptr
sur d'autres threads. Il semble donc toujours être un outil utile pour moi, mais je pourrais négliger quelque chose de fondamental ici.
Pour illustrer ce que j'ai à l'esprit, 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é pourrait être passé à un/des thread(s) différent(s),
// mais la fonction n'est jamais accédée depuis deux threads en même temps
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, donc elle peut être donnée à quelqu'un d'autre
return *it;
} else {
return{};
}
}
private:
std::vector>> _cache;
};
Y a-t-il quelque chose de mal avec cela (si unique()
synchroniserait en réalité avec les destructeurs d'autres copies)?
1 votes
Pourquoi 1 est un cas spécial? Il pourrait y avoir une autre copie créée après votre appel à
unique
et avant que vous ayez terminé ce que vous faites.0 votes
@rubenvb: Si
use_count == 1
, alors il n'y a - par définition - aucun autre thread qui a une référence à partir de laquelle faire une copie.1 votes
@rubenvb: Mon erreur - unique est const, donc un autre thread pourrait faire une copie sans que ce soit 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 effectuées indépendamment.0 votes
@rubenvb: Si ce n'était pas const, vous auriez une course de données en lisant simultanément à 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 : Si je me souviens bien, la bibliothèque standard garantit qu'à condition que vous n'appeliez que des méthodes const sur un objet, il n'y aura pas de course 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épond presque à votre mise à jour. Vous avez raison, si chacun de vos threads n'utilise que des copies d'un shared_ptr partagé, 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 un 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é rendu obsolète, vous pouvez donc continuer à utiliseruse_count()==1
tant que vous vous souvenez que c'est risqué.