2012-06-16 12 views
5

Antes de C++ 11, si tenía una función que operaba en objetos grandes, mi instinto sería escribir funciones con este tipo de prototipo.En C++ 11, ¿aún es necesario pasar una referencia a un objeto que aceptará la salida de una función?

void f(A &return_value, A const &parameter_value); 

(Aquí, valor_devuelto es sólo un objeto en blanco que recibirá la salida de la función. A es sólo algunas clase que es grande y caro para copiar.)

En C++ 11, teniendo ventaja de la semántica movimiento, la recomendación por defecto (como yo lo entiendo) es el más sencillo:

A f(A const &parameter_value); 

¿hay alguna todavía la necesidad de hacerlo de la manera antigua, pasando de un objeto de mantener el valor de retorno?

+1

Pensé que la recomendación predeterminada siempre ha sido "hacer lo que es conveniente y salir de su camino si lo considera necesario" ... – Mehrdad

+3

Nunca hubo tal necesidad, consulte http://cpp-next.com/ archive/2009/08/want-speed-pass-by-value/ –

+0

@ K-ballo: Puede haber una necesidad previa al estándar (aunque las personas tienden a no reconocerlo, C++ existió casi 20 años antes de que se estandarizara) . Aunque tal vez no, estaba empezando a aprender C++ sobre el momento de la estandarización (y ni siquiera sabía que estaba sucediendo en ese momento), ¿existía tal cosa como elisión de copia antes del estándar? ¿Fue utilizado por compiladores populares? –

Respuesta

7

Otros han cubierto el caso donde A podría no tener un constructor de movimiento barato. Supongo que su A sí lo hace. Pero todavía hay una situación más donde es posible que desee pasar un parámetro "out":

Si A es algún tipo como vector o string y se sabe que el parámetro "out" ya cuenta con los recursos (como la memoria) que se puede reutilizar en f, entonces tiene sentido reutilizar ese recurso si puede. Por ejemplo, consideremos:

void get_info(std::string&); 
bool process_info(const std::string&); 

void 
foo() 
{ 
    std::string info; 
    for (bool not_done = true; not_done;) 
    { 
     info.clear(); 
     get_info(info); 
     not_done = process_info(info); 
    } 
} 

vs:

std::string get_info(); 
bool process_info(const std::string&); 

void 
foo() 
{ 
    for (bool not_done = true; not_done;) 
    { 
     std::string info = get_info(); 
     not_done = process_info(info); 
    } 
} 

En el primer caso, la capacidad se acumulará en el string como el bucle se ejecuta, y que la capacidad es entonces potencialmente reutilizado en cada iteración del bucle . En el segundo caso, se asigna un nuevo string en cada iteración (descuidando el pequeño buffer de optimización de cadenas).

Ahora, esto no quiere decir que nunca debe devolver std::string por valor.Solo debe tener en cuenta este problema y aplicar el juicio de ingeniería caso por caso.

3

Es posible que un objeto sea grande y caro de copiar, y cuya semántica de movimiento no puede mejorar al copiar. Consideremos:

struct A { 
    std::array<double,100000> m_data; 
}; 

puede no ser una buena idea para diseñar sus objetos de esta manera, pero si usted tiene un objeto de este tipo por alguna razón y desea escribir una función para rellenar los datos de entonces es posible hazlo usando un param externo.

+1

Podría considerar usar un parámetro out, pero solo porque no quisiera asignar muchos de esos en la pila. Construir un objeto a partir del valor de retorno de la función es tan barato como en cualquier compilador. –

+0

@ eq-: nadie le pide que asigne esto en la pila, 'new f()' es perfectamente aceptable. –

+0

@matthieum. No hay forma de eludir la asignación de la pila cuando una función devuelve un objeto por valor. –

3

Depende: ¿su compilador admite la optimización del valor de retorno, y su función f está diseñada para poder usar el RVO que admite su compilador?

Si es así, entonces sí, de todos modos devuelva por valor. No obtendrá nada en absoluto al pasar un parámetro mutable, y obtendrá una gran cantidad de claridad de código al hacerlo de esta manera. De lo contrario, debe investigar la definición de A.

Para algunos tipos, un movimiento no es más que una copia. Si A no contiene nada que realmente valga la pena mover (punteros que transfieren la propiedad, etc.), entonces no ganará nada moviéndose. Un movimiento no es gratis, después de todo; es simplemente una copia que sabe que todo lo que pertenece al original se está transfiriendo a la copia. Si el tipo no posee nada, entonces un movimiento es solo una copia.

Cuestiones relacionadas