Paso de parámetros a funciones. ¿Copiar o referenciar?

Última actualización: Diciembre de 2019

Supongamos que deseamos comunicar a una función la dirección en memoria de una variable con el fin de modificar su valor. Si descartamos la posibilidad de que el objeto pueda ser inválido (y, con ello, el uso de un puntero tradicional y la subsiguiente comprobación de puntero nulo), declararíamos el parámetro de la función como una referencia lvalue al tipo de variable que se desea modificar. A modo de ejemplo, en el siguiente código creamos un objeto obj de tipo Object, el cual es pasado como argumento a una función do_something() con el fin de realizar ciertas modificaciones sobre el mismo:

   class Object;    // ...    void do_something(Object& ob) { /* la función modifica el objeto referenciado */ }    // ...    auto obj = Object{};    do_something(obj); // llamada a función do_something() para modificar el objeto obj

Recordemos que una referencia lvalue inicializada con una expresión lvalue no es sino un alias, un nombre alternativo para el objeto referenciado: en el código anterior, los cambios efectuados sobre la referencia se realizan realmente sobre el valor referenciado. Ello contrasta claramente con el tradicional paso por valor, en el que se realiza una copia del argumento de la función. Así, según se pase por referencia o por valor, el resultado numérico obtenido en el siguiente código de ejemplo será diferente:

   void f_1(int& i) { ++i; }    void f_2(int i) { ++i; }    //...    auto a = int{0},        b = int{0};    f_1(a);    f_2(b);    std::cout << a; // imprime 1    std::cout << b; // imprime 0

En el primer caso (paso por referencia), el entero nulo a se pasa por referencia a la función f_1(), que incrementa su valor en una unidad. Tras finalizar la ejecución de la función, el nuevo valor de a es la unidad. En el segundo caso (paso por valor), el entero nulo b se pasa por valor a la función f_2(). Ésta opera con una copia independiente de b de nombre i, incrementando su valor. Dicha variable temporal i es destruida al finalizar la ejecución de la función, tras lo cual la variable original b (que no ha sido modificada) sigue valiendo cero.

El paso por valor puede resultar especialmente ineficiente si los argumentos son objetos pesados en memoria. Si deseamos evitar la copia de uno de tales objetos y sabemos que la función no va a modificarlo, debemos optar por utilizar una referencia a un objeto constante:

   void do_something(Object const& ob)    {       /* se evita la copia del objeto y se prohíbe su modificación */    }

Si lo que deseamos es transferir el recurso a la función, renunciando a su propiedad, optaremos por emplear una referencia rvalue:

   void do_something(Object&& ob)    {       /* la función puede hacer efectiva la adquisición del recurso con una        operación std::move(ob) */    }    //...    do_something(std::move(obj));


Referencias bibliográficas:
  • Stroustrup B., 'The C++ Programming Language'. Addison-Wesley, 4th Edition (2013).
  • cppreference - https://en.cppreference.com/book/intro/reference

No hay comentarios:

Publicar un comentario