¿Por qué std::shared ptr:: unique() está obsoleto?
¿Cuál es el problema técnico con std::shared_ptr::unique()
que es la razón de su desaprobación en C++17?
Según cppreference.com, std::shared_ptr::unique()
está en desuso en C++17 como
Esta función está obsoleta a partir de C++17 porque
use_count
es solo una aproximación en un entorno multihilo.
entiendo que esto es cierto para use_count() > 1
: Mientras estoy sosteniendo una referencia a él, alguien puede, simultáneamente, dejar ir de su o crear una nueva copia.
Pero si use_count()
devuelve 1 (que es lo que me interesa cuando llamo a unique()
), entonces no hay otro hilo que pueda cambiar ese valor de una manera picante, así que esperaría que esto sea seguro:{[18]]}
if (myPtr && myPtr.unique()) {
//Modify *myPtr
}
Resultados de mi propia búsqueda:
Encontré este documento: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0521r0.html que propone la desaprobación en respuesta a C++17 CD comment CA 14, pero no pude encontrar dicho comentario en sí.
Como alternativa, ese documento propuso agregar algunas notas, entre ellas las siguientes: {[18]]}
Nota: Cuando múltiples subprocesos pueden afectar el valor de retorno de
use_count()
, el resultado debe ser tratado como aproximado. In particular,use_count() == 1
does not imply that accesses through a previously destroyedshared_ptr
have in any sense completed. - nota final
Entiendo que este podría ser el caso para la forma use_count()
se especifica actualmente (debido a la falta de sincronización garantizada), pero ¿por qué la resolución no se limitó a especificar dicha sincronización y, por lo tanto, hacer que el patrón anterior sea seguro? Si hubiera una limitación fundamental que no permitiera tal sincronización (o la hiciera prohibitivamente costosa), entonces ¿cómo es posible implementar correctamente el destructor?
Actualización:
Pasé por alto el caso obvio presentado por @ alexeykuzmin0 y @ rubenvb, porque hasta ahora solo usé unique()
en instancias de shared_ptr
que eran no es accesible a otros hilos. Así que no había peligro de que esa instancia en particular fuera copiada de una manera picante.
Todavía estaría interesado en saber de qué se trataba exactamente CA 14, porque creo que todos mis casos de uso para unique()
funcionarían siempre y cuando se garantice la sincronización con lo que suceda con diferentes instancias shared_ptr
en otros subprocesos. Así que todavía me parece una herramienta útil, pero podría pasar por alto algo fundamental aquí.
A ilustrar lo que tengo en mente, considere lo siguiente:
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;
};
¿Hay algo malo en ello (si unique()
realmente se sincronizaría con los destructores de otras copias)?
3 answers
Creo que P0521R0resuelve potencialmente carrera de datos mediante el mal uso de shared_ptr
como sincronización entre hilos.
Dice use_count()
devuelve un valor de refcount no fiable, y por lo tanto, unique()
la función miembro será inútil cuando se multiproceso.
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 función miembro unique()
no tiene ningún efecto de sincronización y no hay sucede-antes de relación del destructor de [D] shared_ptr
a [U] llamando a unique()
.
Así que no podemos esperar relación [W] ⇒ [D] ⇒ [U] ⇒ [R] y [W] ⇒ [R]. ('⇒'denota sucede - antes de la relación).
EDITADO: Encontré dos problemas relacionados con LWG; LWG2434. shared_ptr:: use_count() es eficiente, LWG2776. shared_ptr unique () y use_count () . Es solo una especulación, pero el Comité WG21 da prioridad a la implementación existente de la Biblioteca Estándar de C++, por lo que codifican su comportamiento en C++1z.
LWG2434 cita (énfasis mío):
shared_ptr
yweak_ptr
Notas que suuse_count()
podría ser ineficiente. Este es un intento de reconocer las implementaciones reflinked (que pueden ser utilizadas por los punteros inteligentes de Loki, por ejemplo). Sin embargo, no hay ninguna implementaciónshared_ptr
que use reflinking, especialmente después de que C++11 reconociera la existencia de multithreading. Todo el mundo usa refcounts atómicos, así queuse_count()
es solo una carga atómica .
LWG2776 cita (énfasis mío):
El la eliminación de la restricción de "solo depuración" para
use_count()
yunique()
enshared_ptr
por LWG 2434 introdujo un error. Para queunique()
produzca un valor útil y confiable, necesita una cláusula synchronize para garantizar que los accesos anteriores a través de otra referencia sean visibles para el llamante exitoso deunique()
. Muchas implementaciones actuales utilizan una carga relajada, y no proporcionan esta garantía, ya que no está indicada en el estándar. Para el uso de depuración/sugerencia que estaba bien. Sin ella la especificación es poco claro y probablemente engañoso.[...]
Preferiría especificar
use_count()
como solo proporcionar una pista poco fiable de la cuenta real (otra forma de decir solo depurar). O desaprobarlo, como JF sugirió. No podemos haceruse_count()
fiable sin añadir sustancialmente más esgrima. Realmente no queremos que alguien espereuse_count() == 2
para determinar que otro hilo llegó tan lejos. Y desafortunadamente, no creo que actualmente digamos nada para dejar claro que es un error.Esto implicaría que
use_count()
normalmente usamemory_order_relaxed
, y unique no se especifica ni se implementa en términos deuse_count()
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-16 01:41:30
Considere el siguiente código:
// global variable
std::shared_ptr<int> s = std::make_shared<int>();
// thread 1
if (s && s.unique()) {
// modify *s
}
// thread 2
auto s2 = s;
Aquí tenemos una condición de carrera clásica: s2
puede (o no) crearse como una copia de s
en el hilo 2 mientras que el hilo 1 está dentro de if
.
El unique() == true
significa que nadie tiene un shared_ptr
apuntando a la misma memoria, pero no significa que ningún otro hilo tenga acceso a shared_ptr
inicial directamente o a través de punteros o referencias.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-14 12:20:36
Para su placer visual: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0488r0.pdf
Este documento contiene todos los comentarios NB (Organismo Nacional) para la reunión de Issaquah. CA 14 dice:
La eliminación de la restricción "debug only" para use_count () y unique () en shared_ptr introdujo un error: para que unique () producir un valor útil y confiable, necesita una cláusula synchronize para asegurarse de que los accesos anteriores a través de otra referencia son visible a la persona que llama con éxito a unique (). Muchas implementaciones actuales utilizan un carga relajada, y no proporcionan esta garantía, ya que no se indica en el Estándar. Para el uso de depuración / sugerencia que estaba bien. Sin ella el la especificación es confusa y engañosa.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-14 18:01:36