Pregunta

Estaba experimentando con shared_ptr y make_shared de C ++ 11 y programó un pequeño ejemplo de juguete para ver lo que realmente está sucediendo al llamar make_shared. Como infraestructura, estaba usando LLVM/Clang 3.0 junto con la biblioteca LLVM STD C ++ dentro de XCode4.

class Object
{
public:
    Object(const string& str)
    {
        cout << "Constructor " << str << endl;
    }

    Object()
    {
        cout << "Default constructor" << endl;

    }

    ~Object()
    {
        cout << "Destructor" << endl;
    }

    Object(const Object& rhs)
    {
        cout << "Copy constructor..." << endl;
    }
};

void make_shared_example()
{
    cout << "Create smart_ptr using make_shared..." << endl;
    auto ptr_res1 = make_shared<Object>("make_shared");
    cout << "Create smart_ptr using make_shared: done." << endl;

    cout << "Create smart_ptr using new..." << endl;
    shared_ptr<Object> ptr_res2(new Object("new"));
    cout << "Create smart_ptr using new: done." << endl;
}

Ahora eche un vistazo a la salida, por favor:

Crear smart_ptr usando make_shared ...

Constructor make_shared

Constructor de copia ...

Constructor de copia ...

Incinerador de basuras

Incinerador de basuras

Crear smart_ptr usando make_shared: hecho.

Crear smart_ptr usando nuevo ...

Constructor nuevo

Crea smart_ptr usando nuevo: hecho.

Incinerador de basuras

Incinerador de basuras

Parece que make_shared está llamando al constructor de copias dos veces. Si asigno memoria para un Object Usando un regular new Esto no sucede, solo uno Object esta construido.

Lo que me pregunto es lo siguiente. escuché eso make_shared se supone que es más eficiente que usar new(1, 2). Una razón es porque make_shared Asigna el recuento de referencias junto con el objeto que se administrará en el mismo bloque de memoria. Ok, tengo el punto. Por supuesto, esto es más eficiente que dos operaciones de asignación separadas.

Por el contrario, no entiendo por qué esto tiene que venir con el costo de dos llamadas al constructor de copias de Object. Por esto no estoy convencido de que make_shared es más eficiente que la asignación usando new en cada caso. ¿Me equivoco aquí? Bueno, ok, uno podría implementar un constructor de movimiento para Object Pero aún así, no estoy seguro de si esto es más eficiente que solo asignar Object mediante new. Al menos no en todos los casos. Sería cierto si copiar Object es menos costoso que asignar memoria para un contador de referencia. Pero el shared_ptr-La el contador de referencia interno podría implementarse utilizando un par de tipos de datos primitivos, ¿verdad?

¿Puedes ayudar y explicar por qué make_shared ¿Es el camino a seguir en términos de eficiencia, a pesar de la sobrecarga de copias describidas?

¿Fue útil?

Solución

Como infraestructura, estaba usando LLVM/Clang 3.0 junto con la biblioteca LLVM STD C ++ dentro de XCode4.

Bueno, ese parece ser tu problema. El estándar C ++ 11 establece los siguientes requisitos para make_shared<T> (y allocate_shared<T>), en la sección 20.7.2.2.6:

Requiere: La expresión :: nueva (PV) t (std :: hacia adelante (args) ...), donde PV tiene tipo void* y apunta al almacenamiento adecuado para contener un objeto de tipo t, debe estar bien formado. A será un asignador (17.6.3.5). El constructor de copias y el destructor de A no lanzarán excepciones.

T es no requerido para ser constructable para copias. Por cierto, T Ni siquiera se requiere que no sea nuevo construcible. Solo se requiere ser constructable en el lugar. Esto significa que lo único que make_shared<T> puede hacer con T es new está en el lugar.

Por lo tanto, los resultados que obtenga no son consistentes con el estándar. LIBC ++ de LLVM se rompe a este respecto. Presentar un informe de error.

Como referencia, esto es lo que sucedió cuando llevé su código a VC2010:

Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Destructor

También lo porté al original de Boost shared_ptr y make_shared, y obtuve lo mismo que VC2010.

Sugeriría presentar un informe de errores, ya que el comportamiento de LibC ++ está roto.

Otros consejos

Tienes que comparar estas dos versiones:

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

En su código, la segunda variable es solo un puntero desnudo, no un puntero compartido.


Ahora en la carne. make_shared es (en la práctica) más eficiente, porque asigna el bloque de control de referencia junto con el objeto real en una sola asignación dinámica. Por el contrario, el constructor para shared_ptr que toma un puntero de objeto desnudo debe asignar otro Variable dinámica para el recuento de referencias. La compensación es que make_shared (o su primo allocate_shared) no le permite especificar un delector personalizado, ya que el asignador realiza la asignación.

(Esto no afecta la construcción del objeto en sí. ObjectPerspectiva de 'No hay diferencia entre las dos versiones. Lo que es más eficiente es el puntero compartido en sí, no el objeto administrado).

Entonces, una cosa a tener en cuenta es su configuración de optimización. Medir el rendimiento, particularmente con respecto a C ++ es sin sentido sin optimizaciones habilitadas. No sé si de hecho compilaste con optimizaciones, así que pensé que valía la pena mencionarlo.

Dicho esto, lo que está midiendo con esta prueba es no una forma que make_shared es más eficiente. En pocas palabras, está midiendo lo incorrecto:-P.

Aquí está el trato. Normalmente, cuando crea un puntero compartido, tiene al menos 2 miembros de datos (posiblemente más). Uno para el puntero y otro para el recuento de referencias. Este recuento de referencia se asigna en el montón (para que se pueda compartir entre shared_ptr con diferentes vidas ... ¡ese es el punto después de todo!)

Entonces, si está creando un objeto con algo como std::shared_ptr<Object> p2(new Object("foo")); Hay por lo menos 2 llamadas a new. Uno para Object y uno para el objeto de conteo de referencia.

make_shared tiene la opción (no estoy seguro de que tenga que), para hacer un solo new que es lo suficientemente grande como para mantener el objeto apuntado y el recuento de referencia en el mismo bloque contiguo. Asignar efectivamente un objeto que se vea como esto (ilustrativo, no literalmente lo que es).

struct T {
    int reference_count;
    Object object;
};

Dado que el recuento de referencia y las vidas del objeto están unidas (no tiene sentido que uno viva más que el otro). Todo este bloque puede ser deleteD al mismo tiempo también.

Entonces, la eficiencia está en las asignaciones, no en la copia (lo que sospecho que tuvo que ver con la optimización más que cualquier otra cosa).

Para ser claros, esto es lo que Boost tiene que decir sobre make_shared

http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/make_shared.html

Además de la conveniencia y el estilo, dicha función también es una excepción segura y considerablemente más rápida porque puede usar una sola asignación tanto para el objeto como para su bloque de control correspondiente, eliminando una parte significativa de la sobrecarga de construcción de Shared_PTR. Esto elimina una de las principales quejas de eficiencia sobre Shared_PTR.

No debería recibir copias adicionales allí. La salida debe ser:

Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor

No sé por qué estás recibiendo copias adicionales. (Aunque veo que obtienes un 'Destructor' demasiado, por lo que el código que usó para obtener tu salida debe ser diferente del código que publicó)

make_shared es más eficiente porque se puede implementar utilizando solo una asignación dinámica en lugar de dos, y porque necesita un valor de memoria de un puntero menos contabilidad por objeto compartido.

Editar: no verifiqué con Xcode 4.2 pero con Xcode 4.3 obtengo la salida correcta que muestro arriba, no la salida incorrecta que se muestra en la pregunta.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top