smart - unique_ptr c++ reference




¿Cómo puede unique_ptr no tener gastos generales si necesita almacenar el eliminador? (3)

Introducción breve:

unique_ptr puede introducir una pequeña sobrecarga, pero no debido al borrado, sino porque cuando se mueve desde él, el valor debe establecerse en nulo, donde si estuviera usando punteros sin procesar, podría dejar el puntero antiguo en un estado propenso a errores pero legítimo donde aún apunta a donde apuntaba antes. Obviamente, el optimizador inteligente puede optimizar, pero no está garantizado.

De vuelta al eliminador:

Otras respuestas son correctas, pero elaboradas. Así que aquí está la versión simplificada sin mención de EBO u otros términos complicados.

Si el borrado está vacío (no tiene estado), no es necesario mantenerlo dentro de unique_ptr. Si lo necesitas, puedes construirlo cuando lo necesites. Todo lo que necesita saber es el tipo de eliminación (y ese es uno de los argumentos de la plantilla para unique_ptr).

Por ejemplo, considere el siguiente código, que también demuestra la creación simple a petición de un objeto sin estado.

#include <iostream>
#include <string>
#include <string_view>

template<typename Person>
struct Greeter{
    void greet(){
        static_assert(std::is_empty_v<Person>, "Person must be stateless");
        Person p; // Stateless Person instance constructed on demand
        std::cout << "Hello " << p() << std::endl;
    }
    // ... and not kept as a member.
};

struct Bjarne{
    std::string_view operator()(){
        return "Bjarne";
    }
};

int main() {
    Greeter<Bjarne> hello_bjarne;
    hello_bjarne.greet();
}

Primero, eche un vistazo a lo que C ++ Primer dijo acerca de unique_ptr y shared_ptr :
$ 16.1.6. Eficiencia y flexibilidad

Podemos estar seguros de que shared_ptr no mantiene el eliminador como miembro directo, porque el tipo de eliminador no se conoce hasta el tiempo de ejecución.

Debido a que el tipo del eliminador es parte del tipo de unique_ptr , el tipo del miembro del eliminador se conoce en el momento de la compilación. El eliminador se puede almacenar directamente en cada objeto unique_ptr .

Así que parece que shared_ptr no tiene un miembro directo de deleter, pero unique_ptr sí. Sin embargo, la respuesta más votada de otra pregunta dice:

Si proporciona el borrador como argumento de plantilla (como en unique_ptr ) es parte del tipo y no necesita almacenar nada adicional en los objetos de este tipo . Si se pasa deleter como argumento del constructor (como en shared_ptr ) , debe almacenarlo en el objeto . Este es el costo de la flexibilidad adicional, ya que puede usar diferentes eliminaciones para los objetos del mismo tipo.

Los dos párrafos citados son totalmente conflictivos, lo que me confunde. Lo que es más, mucha gente dice que unique_ptr tiene cero gastos generales porque no necesita almacenar el eliminador como miembro. Sin embargo, como sabemos, unique_ptr tiene un constructor de unique_ptr<obj,del> p(new obj,fcn) , lo que significa que podemos pasarle un eliminador, por lo que unique_ptr parece haber almacenado el eliminador como miembro. ¡Que desastre!


La frase clave que parece confundirlo es "El eliminador se puede almacenar directamente". Pero no tiene sentido almacenar un borrador de tipo std::default_delete . Si necesita uno, puede crear uno como std::default_delete{} .

En general, los borradores sin estado no necesitan ser almacenados, ya que puede crearlos a pedido.


La respuesta de Angew explicó bastante a fondo lo que está pasando.

Para aquellos curiosos cómo podrían verse las cosas bajo las sábanas.

template<typename T, typename D, bool Empty = std::is_empty_v<D>>
class unique_ptr
{
    T* ptr;
    D d;

    // ... 
};

template<typename T, typename D>
class unique_ptr<T, D, true> : D
{
    T* ptr;

    // ...
};

Que se especializa para los borrados vacíos y aprovechar la optimización de la base vacía .





c++-standard-library