c++ - ¿Por qué los eliminadores de shared_ptr deben ser CopyConstructible?
c++11 language-lawyer (3)
Debido a que shared_ptr está destinado a ser copiado, y cualquiera de esas copias podría tener que eliminar el objeto, entonces todas deben tener acceso a un eliminador. Mantener solo un eliminador requeriría, bueno, volver a contar el propio eliminador. Si realmente quieres que eso suceda, podrías usar un std::shared_ptr como el eliminador, pero eso suena un poco exagerado.
En C ++ 11 std::shared_ptr tiene cuatro constructores a los que se pueden pasar los objetos de eliminación d del tipo D Las firmas de estos constructores son las siguientes:
template<class Y, class D> shared_ptr(Y * p, D d);
template<class Y, class D, class A> shared_ptr(Y * p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);
El estándar requiere en [util.smartptr.shared.const] tipo D que sea CopyConstructible. ¿Por qué es esto necesario? Si shared_ptr hace copias de d , ¿a cuál de estos deleters se le podría llamar? ¿No sería posible que shared_ptr solo mantuviera un solo eliminador alrededor? ¿Qué significa para shared_ptr poseer un eliminador si d se puede copiar?
¿Cuál es la razón de ser del requisito de CopyConstructible?
PD: este requisito podría complicar la escritura de eliminadores para shared_ptr . unique_ptr parece tener requisitos mucho mejores para su eliminador.
Esta pregunta me desconcertaba tanto que boost::shared_ptr correo electrónico a Peter Dimov (implementador de boost::shared_ptr e involucrado en la estandarización de std::shared_ptr )
Aquí está la esencia de lo que dijo (reimpreso con su permiso):
Mi suposición es que el Deleter tenía que ser CopyConstructible realmente solo como una reliquia de C ++ 03 donde la semántica de movimiento no existía.
Tu conjetura es correcta. Cuando se especificó shared_ptr, las referencias de valor aún no existían. Hoy en día deberíamos ser capaces de salir adelante con la exigencia de no mover movimiento-construible.
Hay una sutileza en eso cuando
pi_ = new sp_counted_impl_pd<P, D>(p, d);throws,
ddebe dejarse intacto para que la limpiezad(p)funcione, pero creo que esto no sería un problema (aunque en realidad no he intentado que la implementación sea amigable).
[...]
Creo que no habrá ningún problema para que la implementación lo defina de modo que cuando losnewlanzamientos,dse dejen en su estado original.Si vamos más allá y permitimos que
Dtenga un constructor de movimientos arrojadizos, las cosas se vuelven más complicadas. Pero no lo haremos :-)
La diferencia entre los modificadores en std::shared_ptr y std::unique_ptr es que el shared_ptr shared_ptr está borrado por tipo, mientras que en el tipo de unique_ptr unique_ptr es parte de la plantilla.
Aquí está Stephan T. Lavavej explicando cómo el borrado de tipos conduce al requisito CopyConstructible en std::function .
En cuanto a la razón detrás de esta diferencia en los tipos de punteros, se ha abordado en SO varias veces, por ejemplo here .
Una cita de lo que dijo STL:
Muy sorprendente "gotcha", diría yo, es que la
std::functionrequiere objetos de función CopyConstructible, y esto es algo inusual en el STL.Usualmente el STL es flojo en el sentido de que no necesita las cosas por adelantado: si tengo algo así como una
std::listde tipoT,Tno necesita ser menos que comparable; solo si llama a lalist<T>::sortfunciones miembrolist<T>::sortentonces realmente necesita ser menos que comparable.La regla del lenguaje central que potencia esto es que las definiciones de las funciones miembro de una plantilla de clase no se instancian hasta que realmente se necesitan y los cuerpos no existen en algún sentido hasta que realmente se lo llama.
Esto generalmente es genial, esto significa que solo paga por lo que necesita, pero la
std::functiones especial debido a la borradura de tipo, porque cuando construye lastd::functionpartir de un objeto llamableF, necesita generar todo lo que pueda necesitar. de ese objetoFporque va a borrar su tipo. Requiere todas las operaciones que posiblemente podría necesitar, independientemente de si se usan.Por lo tanto, si construye una
std::functiondesde algún objeto llamableF,Fes absolutamente necesario en tiempo de compilación para ser CopyConstructible. Esto es cierto incluso aunqueFse mueva a lastd::function, incluso si le da un valor r e incluso si nunca copia lasstd::functionen cualquier lugar de su programa,Ftodavía debe ser CopyConstructible.Obtendrá un error de compilación al decirlo, tal vez horrible, tal vez agradable, dependiendo de lo que obtenga.
Simplemente no puede almacenar objetos de función solo móviles. Esta es una limitación de diseño causada en parte por el hecho de que
std::functionse remonta a boost / TR1, antes de las referencias de valor r, y en cierto sentido nunca se puede arreglar con la interfaz destd::functiontal como está.Se están investigando alternativas, tal vez podamos tener una "función móvil" diferente, por lo que probablemente obtengamos algún tipo de envoltorio borrado por tipo que pueda almacenar funciones solo móviles en el futuro, pero
std::functiontal como está en c ++ 17 en este momento no puede hacer eso, así que ten en cuenta.