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
n'est qu'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 pourrait simultanément la lâcher 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 façon hasardeuse, 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 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.
Comme alternative, ce document proposait d'ajouter quelques notes, y compris les suivantes :
Note : Lorsque plusieurs threads peuvent affecter la valeur de retour de
use_count()
, le résultat devrait ê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 de quelque manière que ce soit. — 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 de rendre ainsi le modèle ci-dessus sûr ? S'il y avait une limitation fondamentale qui n'aurait pas permis une telle synchronisation (ou la rendait prohibitivement coûteuse), comment serait-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. Il n'y avait donc aucun danger que cette instance particulière soit copiée de manière hasardeuse.
Je serais toujours intéressé de savoir de quoi CA 14 parlait exactement, car je crois que tous mes cas d'utilisation pour 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. Cela me semble donc toujours être un outil utile, mais je pourrais négliger 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é pourrait être transmis à un/des thread(s) différents,
// mais la fonction n'est jamais accédée par 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 qui ne va pas (si unique()
synchronise en réalité avec les destructeurs d'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 ayez terminé 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 qui possède une référence à partir de laquelle il pourrait faire une copie.1 votes
@rubenvb : Mon erreur - unique est constant, donc un autre thread pourrait faire une copie sans que ce soit une course de données
1 votes
const
n'a aucun rapport 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 créées indépendamment.0 votes
@rubenvb: Si ce n'était pas const, alors vous auriez des dataraces en lisant 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 : Si je me souviens bien, 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 fils ne fait que des copies d'un shared_ptr partagé, alors unique() fonctionnera bien. Dans le document que vous citez et dans la réponse de alexeykuzmin0, plusieurs fils partagent une référence à un unique shared_ptr... 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é.