Pourquoi std:: shared ptr:: unique () est-il obsolète?
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
Cette fonction est obsolète à partir de C++17 Car
use_count
n'est qu'une approximation dans un environnement multi-thread.
je comprends que cela soit vrai pour use_count() > 1
: pendant que je tiens une référence, quelqu'un d'autre pourrait simultanément lâcher la sienne ou créer une nouvelle copie.
Mais si use_count()
renvoie 1 (ce qui m'intéresse lors de l'appel de unique()
), Il n'y a pas d'autre thread qui pourrait changer cette valeur de manière racée, donc je m'attendrais à ce que cela soit sûr:
if (myPtr && myPtr.unique()) {
//Modify *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 à C++17 CD comment CA 14 , mais je n'ai pas pu trouver dit le commentaire lui-même.
Comme alternative, ce document a proposé d'ajouter quelques notes, y compris les suivantes:
Remarque: Lorsque plusieurs threads peuvent affecter la valeur de retour de
use_count()
, le résultat doit être traité comme approximatif. en particulier,use_count() == 1
n'implique pas que les accès par unshared_ptr
précédemment détruits ont en aucun cas terminé. - note de fin
Je comprends que cela pourrait être le cas pour la façon dont use_count()
est actuellement spécifié (due à l'absence de synchronisation garantie), mais pourquoi la résolution n'a-t-elle pas seulement pour spécifier une telle synchronisation et donc rendre le modèle ci-dessus sûr? S'il y avait une limitation fondamentale qui ne permettrait pas une telle synchronisation (ou la rendrait prohibitivement coûteuse), alors 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 je n'ai utilisé que unique()
sur les instances de {[10] } qui étaient non accessible aux autres threads eux-mêmes. Il n'y avait donc aucun danger que cette instance particulière soit copiée de manière racée.
Je serais toujours intéressé d'entendre ce qu'était exactement CA 14, car je crois que tous mes cas d'utilisation pour unique()
fonctionneraient tant qu'il est garanti de se synchroniser avec tout ce qui arrive à différentes instances shared_ptr
sur d'autres threads. Donc, cela me semble toujours être un outil utile, mais je pourrais négliger quelque chose de fondamental ici.
À 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<std::array<uint8_t, 256>>();
}
}
// the returned chunk of memory might be passed to a different thread(s),
// but the function is never accessed from two threads at the same time
std::shared_ptr<std::array<uint8_t,256>> getChunk()
{
auto it = std::find_if(_cache.begin(), _cache.end(), [](auto& ptr) { return ptr.unique(); });
if (it != _cache.end()) {
//memory is no longer used by previous user, so it can be given to someone else
return *it;
} else {
return{};
}
}
private:
std::vector<std::shared_ptr<std::array<uint8_t, 256>>> _cache;
};
Y a - t-il quelque chose de mal (Si unique()
se synchronisait réellement avec les destructeurs d'autres copies)?
3 réponses
Je pense que P0521R0 résout potentiellement course de données en abusant de shared_ptr
comme synchronisation inter-thread.
Il dit use_count()
renvoie une valeur de refcount non fiable, et donc, unique()
la fonction membre sera inutile lors du multithreading.
int main() {
int result = 0;
auto sp1 = std::make_shared<int>(0); // refcount: 1
// Start another thread
std::thread another_thread([&result, sp2 = sp1]{ // refcount: 1 -> 2
result = 42; // [W] store to result
// [D] expire sp2 scope, and refcount: 2 -> 1
});
// Do multithreading stuff:
// Other threads may concurrently increment/decrement refcounf.
if (sp1.unique()) { // [U] refcount == 1?
assert(result == 42); // [R] read from result
// This [R] read action cause data race w.r.t [W] write action.
}
another_thread.join();
// Side note: thread termination and join() member function
// have happens-before relationship, so [W] happens-before [R]
// and there is no data race on following read action.
assert(result == 42);
}
La fonction membre unique()
n'a aucun effet de synchronisation et il n'y a pas se produit-avant la relation du destructeur de [D] shared_ptr
à [U] appelant unique()
.
Nous ne pouvons donc pas nous attendre à une relation [W] ⇒ [D] ⇒ [U] ⇒ [R] et [W] ⇒ [R]. ('⇒'indique se produit-avant la relation).
Édité: j'ai trouvé deux problèmes liés au LWG; LWG2434. shared_ptr:: use_count() est efficace, LWG2776. shared_ptr unique () et use_count () . C'est juste une spéculation, mais le Comité wg21 donne la priorité à l'implémentation existante de la bibliothèque Standard C++, donc ils codifient son comportement en C++1z.
Lwg2434 citation (emphase mine):
shared_ptr
etweak_ptr
avoir des Notes que leuruse_count()
pourrait être inefficace. ceci est une tentative de reconnaître les implémentations reflinked (qui peuvent être utilisées par les pointeurs Loki smart, par exemple). cependant, il n'y a pas d'implémentationsshared_ptr
qui utilisent reflinking, surtout après que C++11 ait reconnu L'existence du multithreading. Tout le monde utilise des refcounts atomiques, doncuse_count()
est juste une charge atomique .
Lwg2776 citation (emphase mine):
Le la suppression de la restriction "debug only" pour
use_count()
etunique()
dansshared_ptr
par LWG 2434 a introduit un bug. Pour queunique()
produise une valeur utile et fiable, il a besoin d'une clause synchronize pour s'assurer que les accès antérieurs via une autre référence sont visibles pour l'appelant réussi deunique()
. de nombreuses implémentations actuelles utilisent une charge détendue, et ne fournissent pas cette garantie, car elle n'est pas indiquée dans la norme. pour l'utilisation de débogage / indice qui était OK. Sans elle la spécification est peu clair et probablement trompeur.[...]
Je préférerais spécifier
use_count()
comme ne fournissant qu'un indice peu fiable du nombre réel (une autre façon de dire déboguer seulement). Ou le déprécier, comme JF l'a suggéré. nous ne pouvons pas rendreuse_count()
fiable sans ajouter beaucoup plus de clôtures. Nous ne voulons vraiment pas que quelqu'un attendeuse_count() == 2
pour déterminer qu'un autre thread est allé aussi loin. Et malheureusement, je ne pense pas que nous disons actuellement quoi que ce soit pour préciser que c'est un erreur.Cela impliquerait que
use_count()
utilise normalementmemory_order_relaxed
, et unique n'est ni spécifié ni implémenté en termes deuse_count()
.
Considérons le code suivant:
// global variable
std::shared_ptr<int> s = std::make_shared<int>();
// thread 1
if (s && s.unique()) {
// modify *s
}
// thread 2
auto s2 = s;
Ici, nous avons une condition de course classique: s2
peut (ou non) être créé comme une copie de s
dans le thread 2 tandis que le thread 1 est à l'intérieur du if
.
Le unique() == true
signifie que personne n'a un shared_ptr
pointant vers la même mémoire, mais ne signifie pas que les autres threads n'ont pas accès à shared_ptr
initial directement ou via des pointeurs ou des références.
Pour votre plus grand plaisir: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0488r0.pdf
Ce document contient tous les commentaires du NB (organisme National) pour la réunion D'Issaquah. CA 14 se lit comme suit:
La suppression de la restriction "debug only" pour use_count () et unique() dans shared_ptr introduit un bug: pour unique () à produire une valeur utile et fiable, il a besoin d'une clause de synchronisation pour assurez - vous que les accès antérieurs via une autre référence sont visible à l'appelant réussi de unique (). De nombreuses implémentations actuelles utilisent un charge détendue, et ne fournissent pas cette garantie, car il n'est pas indiqué dans la Norme. Pour l'utilisation de débogage/indice qui était OK. Sans elle, l' la spécification n'est pas claire et trompeuse.