2010-07-09 13 views
11

Estoy tratando de entender si hay algún beneficio para devolver una referencia const. Tengo una función factorial que normalmente se ve así:C++ Pase por Const Referencia y devolución por Const Referencia

unsigned long factorial(unsigned long n) 
{ 
    return (n == 0) ? 1 : n * factorial(n - 1); 
} 

Estoy asumiendo que habrá un aumento de rendimiento cuando se pasa por referencia const y devolver una referencia const ... pero const -correctness siempre me confunde.

const unsigned long & factorial(const unsigned long& n) 
{ 
    return (n == 0) ? 1 : n * factorial(n - 1); 
} 

¿Es válido para devolver una referencia const? Además, podría alguien decirme por favor: ¿es beneficioso?

+0

¿Qué sucede cuando el valor de retorno se sale del alcance (es decir, al final de la función)? Supongo que la referencia apunta a la memoria no válida. – robinjam

+1

Es poco probable que pasar 'largo' por referencia directa sea beneficioso para pasarlo por valor. En general, esto es específico de la implementación, pero en todas las arquitecturas en las que pueda pensar, tanto 'long' como' long & 'se representarán como cantidades de 32 bits, o ambas serán de 64 bits. Entonces copia la misma cantidad de datos, pero ahora la persona que llama tiene que calcular la dirección (lo que significa que también debe tener un local, y no solo quedarse en el registro), y el destinatario tiene que eliminar la referencia. En el mejor de los casos, la función se insertaría y el optimizador descartaría la referencia. –

+0

Así que estoy seguro si solo hago que los parámetros de retorno y de entrada sean const (sin referencias, solo const values). – Kiril

Respuesta

9

Esto no es válido. No puede devolver referencia a una variable local. compilador

MSVS C++ incluso da la siguiente advertencia:

main.cc : warning C4172: returning address of local variable or temporary 

No del todo seguro acerca de GCC, pero probablemente el resultado sería el mismo.

+0

OK, pero es seguro suponer que una optimización válida sería hacer referencia al parámetro de entrada, porque he oído que no hay mucho beneficio para pasar const por referencia para los tipos incorporados (como long). Y al menos debería hacer el tipo de retorno const (sin la referencia)? – Kiril

+3

La regla de oro es: cuando conoces las primitivas de paso de tipo por valor, las clases de paso que encapsulan una sola primitiva por valor pasan todo lo demás por referencia. Para clases opacas de terceros, indique lo que su documentación indique que debe hacer (por ejemplo, iteradores: paso por valor). Cuando no se conoce el tipo por adelantado (p. Ej., Es un parámetro de tipo de plantilla), pase por referencia constante y espere que el optimizador lo despoje donde no lo necesite :) –

+0

@Lirik Personalmente utilizo 'const' solo cuando uso' const 'referencias y solo en clases. De lo contrario, simplemente no tienen sentido. Acerca de la optimización - optimizar y perfil. Las referencias aquí (en código ensamblador) a veces pueden mejorar las cosas y, a veces, empeorar. También podría estar interesado en esto: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value –

6

La referencia const es incorrecta aquí; está devolviendo una referencia a una variable local, un temporal no nombrado aquí, 1 o el resultado de n * factorial(n - 1). Debido a que la referencia es a una variable local en la función, para el momento en que la referencia llega a la persona que llama, esa variable local ya ha salido del alcance y no es válida.

Devuelve una referencia directa a los tipos estructurados grandes en los que desea evitar una copia cuando la referencia sobreviva a la salida de la función. Por lo general, esto significa devolver una referencia a un argumento o a una variable miembro (en el caso de las clases).

8

Una referencia const no es más rápida que un valor, si el tamaño del valor es pequeño. En este caso, el tipo del valor es long, que es IMO pequeño (por ejemplo, de 4 a 8 bytes): por lo tanto, una referencia constante no será más rápida. De hecho, puede ser más lento, porque para obtener el valor de una referencia, el compilador puede necesitar emitir el código que desreferenciará la referencia (como desreferenciar un puntero).

Dado que una referencia se implementa (internamente) como un puntero, esperaría obtener un mejor rendimiento al pasar referencias que pasar valores cuando el tamaño del valor sea mayor que el de un puntero (suponiendo que sea incluso legal para pasar una referencia: una referencia a una variable local que está fuera del alcance no es legal).

+2

Un puntero/referencia a una variable interna * es * válido durante la vida de la expresión, esta es la razón por la cual std :: string 's .c_str() y operator = funciona. También es por qué const char * monkey = myStr.c_str(); luego, usar el puntero 'mono' en otro lugar del código es una mala idea. –

1

Posible aún nunca haría eso. ¿Por qué?

Porque esta es una buena manera de hacer que su código no sea legible.

Además, el compilador optimizará sin este truco.

Y adicionalmente, ¿No tiene otros "cuellos de botella" para optimizar en su programa?
Quiero decir, si toma esta parte de su código y lo mira en conjunto, verá que pasar el valor a la función y obtener el resultado no es más que unos pocos códigos de operación. ¿Cómo?
Bueno, un entero de 32 bits encajará en un registro. Rápido como "mov eax, ...".
Por otro lado, su programa probablemente tenga otros problemas de diseño/algoritmo que podrían optimizarse ... A menos que sea tan simple como un programa "hello world".

Por lo tanto, llegar a estas cosas no es algo que haría, y todos son bienvenidos a desafiarme.

+0

+1 para "una excelente manera de hacer que el código sea ilegible" y una IMO verdaderamente descompuesta.He trabajado en tales proyectos, créanme que crea miseria :-( –

+0

La pregunta claramente establece que no se trata de la optimización del código del programa (o legibilidad), sino de * entender * las referencias y sus usos. Y sin pruebas, no lo hago confíe en que "el compilador lo optimizará de todos modos". Tiene razón acerca de la legibilidad, pero a veces debe hacer lo que tiene que hacer, especialmente en una lengua como C++. – Kissaki

3

El único caso donde es válido regresar por referencia es si el objeto que está devolviendo sobrevivirá a la llamada de función * (como devolver un miembro de la clase en la que se ha invocado la función), e incluso en ese contexto, si uno debe hacer eso es dudoso, ya que eso permitirá que dos personas que llaman diferentes accedan a la misma ubicación de memoria, lo que hace que las cosas sean una pesadilla para arreglar si usa múltiples hilos.

* NOTA: En su caso, el elemento que está devolviendo es una variable local y, por lo tanto, no sobrevivirá a la llamada a la función. Por lo tanto, el código que ha proporcionado invoca el malvado comportamiento indefinido.

Cuestiones relacionadas