Pregunta

Si un C ++ (implícita o explícita) constructor de datos acepta su parámetro (s) por valor o referencia a const, cuando se van a almacenar una copia del argumento (s) en su objeto de cualquier manera?

Este es el ejemplo más corto que puedo pensar en:

struct foo {
    bar _b;
    foo(bar [const&] b) // pass by value or reference-to-const?
        : _b(b) { }
};

La idea aquí es que quiero para minimizar las llamadas al constructor de copia del bar cuando se crea un objeto foo, en cualquiera de las diversas formas en que un objeto foo podría obtener creadas.

Tenga en cuenta que lo hago saber un poco acerca de la elisión de copia y (nombre) Optimización valor de retorno, y he leído '¿Quieres velocidad? paso por valor' , sin embargo no creo que el artículo se dirige directamente a este caso de uso.

Editar:. que debería ser más específico

asumir que no puedo saber la sizeof(bar), o si o no bar es un elemento fundamental, tipo incorporado (bar puede ser un parámetro de plantilla, y foo Puede que sea una plantilla de clase en lugar de una clase). Además, no asuma que el constructor de foo puede ser inline (o de bar, para el caso). No asumir que al menos que esté utilizando un compilador que implementa RVO.

Lo que me gustaría es para que haya una posibilidad (optimizaciones del compilador dados) que una llamada como ésta invocará ninguna llamada a constructor de copia de bar que sea (incluso cuando se ejecuta _b(b) en la lista de inicialización de foo):

foo f = function_that_creates_and_returns_a_bar_object_using_rvo();

¿Hay alguna posibilidad (dado el estándar de C ++ 98) que esto se puede hacer, y si es así, es más o menos probable que funcione si foo acepta su parámetro por referencia a const en lugar de por el valor de?

¿Fue útil?

Solución

En C ++ 98 ++ y C 03, que debe pasar const& bar y luego copiar. En C ++ 0x, debe pasar bar y luego hacer un movimiento (siempre bar tiene un constructor movimiento).

#include <utility>

struct foo
{
    bar _b;

    foo(bar b) : _b(std::move(b)) {}
};

Si construye foo con un parámetro de valor-I, el constructor de copia se llamará a crear una copia b, y que copia se trasladó a _b. Si construye foo con un parámetro de valor de lado derecho, mover el constructor de bar se llamará a moverse en b, y luego se trasladó de nuevo a _b.

Otros consejos

En igualdad de condiciones, paso por const y para las clases suficientemente complejos, y el valor para POD y objetos simples.

Trazado de las ventajas / desventajas para pasar por referencia const en lugar de pase tradicional por valor

Lo positivo:

  • Evita una copia (gran ventaja para los objetos con una copia caro)
  • Acceso de sólo lectura

Negativos:

  • Alguien puede emitir el const const de la referencia si realmente quisieran

Lo más importante con los aspectos positivos es explícitamente de control cuando la copia se produce (en su caso, al pasar inicializar _b en su lista de inicialización). Pensando en los negativos ... Estoy de acuerdo que es un riesgo. Creo que casi todos los buenos programadores se sentirán sucia sobre la emplying const_cast. Por otra parte se puede buscar diligentemente const_cast y tirar tomates a quien ha emitido la const fuera de la discusión. Pero bueno nunca se sabe y que tiene tiempo para ver el código como un halcón:?)

Mi opinión subjetiva es que en las clases lo suficientemente complejos y en entornos en los que el rendimiento importa el beneficio de evitar el constructor de copia mayor que el riesgo. Sin embargo, para las clases realmente tontas y POD, tiendo a hacer copias de los datos y pasar por valor.

.

Uso const T & arg si sizeof(T)>sizeof(void*) y utilizar T arg si sizeof(T) <= sizeof(void*). Todos los tipos fundamentales deben ser la excepción a esta regla

Estilísticamente, yo diría que el paso por referencia es la mejor manera.

Si el rendimiento que realmente importa, entonces no adivine. Medirlo.

No he comprobado lo que dice la norma, pero al tratar de esta manera empírica que se puede decir que GCC no optimiza la copia de distancia, independientemente de si el constructor acepta el argumento por valor o por referencia constante.

Sin embargo, si el constructor toma una referencia constante, se las arregla para evitar una copia innecesaria cuando se crea foo a partir de una existente bar objeto.

Para resumir:

Bar b = makeBar();         // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar();   // Copy constructor of Bar called once
FooByValue f3(b);          // Copy constructor of Bar called twice
FooByRef f4(b);            // Copy constructor of Bar called only once

No es que yo soy un experto compilador, pero supongo que tiene sentido que por lo general no puede RVO un valor de retorno en un lugar arbitrario (como el campo miembro del objeto foo). En lugar de ello, se requiere el objetivo de estar en la parte superior de la pila.

Asumo bar tiene un constructor de copia normal de la forma bar(const bar &b)

Tome una referencia constante aquí. El constructor de bar tomará esa referencia, y realizar la copia. copias totales: 1.

Si usted dejó fuera de la referencia, el compilador podría hacer una copia de b, pasar la copia al constructor de foo, que luego pasarlo a bar y copiar eso.

Sinceramente, para este caso, la mejor opción es hacer que su inline constructor, constructor proporcionado bar no tira. Entonces sólo tiene que pasar por referencia.

Además, como se sospechaba, el artículo no se aplica aquí.

Mantenga un shared_pointer a la barra en su clase y pasarlo como tales. De esta manera nunca se llama al constructor de copia:)

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