¿Cómo pasar objetos a funciones en C ++?

Soy nuevo en la progtwigción de C ++, pero tengo experiencia en Java. Necesito una guía sobre cómo pasar objetos a funciones en C ++.

¿Debo pasar punteros, referencias o valores que no sean de puntero ni de referencia? Recuerdo que en Java no existen tales problemas ya que solo pasamos la variable que contiene referencia a los objetos.

Sería genial si también pudieras explicar dónde usar cada una de esas opciones.

Reglas generales para C ++ 11:

Pase por valor , excepto cuando

  1. no necesita la propiedad del objeto y un simple alias servirá, en cuyo caso pasará por referencia de referencia ,
  2. debe mutar el objeto, en cuyo caso, use pasar por una referencia de valor l no const ,
  3. pasas objetos de clases derivadas como clases base, en cuyo caso debes pasar por referencia . (Use las reglas previas para determinar si pasar por referencia const o no).

Pasar por el puntero casi nunca se aconseja. Los parámetros opcionales se expresan mejor como boost::optional , y el alias se hace bien por referencia.

La semántica de movimiento de C ++ 11 hace que pasar y regresar por valor sea mucho más atractivo incluso para objetos complejos.


Reglas generales para C ++ 03:

Pasar argumentos por referencia const , excepto cuando

  1. se deben cambiar dentro de la función y dichos cambios deben reflejarse en el exterior, en cuyo caso se pasa por referencia no const
  2. la función debe ser invocable sin ningún argumento, en cuyo caso se pasa por el puntero, de modo que los usuarios pueden pasar NULL / 0 / nullptr lugar; aplica la regla anterior para determinar si debes pasar por un puntero a un argumento const
  3. son de tipos integrados, que se pueden pasar por copia
  4. deben cambiarse dentro de la función y dichos cambios no deben reflejarse en el exterior, en cuyo caso puede pasar por copia (una alternativa sería pasar de acuerdo con las reglas anteriores y hacer una copia dentro de la función)

(aquí, “pasar por valor” se llama “pasar por copia”, porque pasar por valor siempre crea una copia en C ++ 03)


Hay más en esto, pero estas pocas reglas para principiantes te llevarán bastante lejos.

Existen algunas diferencias en las convenciones de llamadas en C ++ y Java. En C ++, técnicamente hablando, hay solo dos convenciones: pasar por valor y pasar por referencia, con algo de literatura que incluye una tercera convención de pase por puntero (que en realidad es pasar por valor de un tipo de puntero). Además de eso, puedes agregar const-ness al tipo de argumento, mejorando la semántica.

Pase por referencia

Pasar por referencia significa que la función recibirá conceptualmente su instancia de objeto y no una copia de la misma. La referencia es conceptualmente un alias del objeto que se usó en el contexto de llamada, y no puede ser nulo. Todas las operaciones realizadas dentro de la función se aplican al objeto fuera de la función. Esta convención no está disponible en Java o C.

Pase por valor (y pase por puntero)

El comstackdor generará una copia del objeto en el contexto de llamada y usará esa copia dentro de la función. Todas las operaciones realizadas dentro de la función se realizan a la copia, no al elemento externo. Esta es la convención para tipos primitivos en Java.

Una versión especial de ella está pasando un puntero (dirección del objeto) a una función. La función recibe el puntero y todas y cada una de las operaciones aplicadas al puntero se aplican a la copia (puntero); por otro lado, las operaciones aplicadas al puntero desreferenciado se aplicarán a la instancia del objeto en esa ubicación de memoria, por lo que la función puede tener efectos secundarios El efecto de usar el valor de paso de un puntero al objeto permitirá que la función interna modifique valores externos, como con la referencia de paso a paso y también permitirá valores opcionales (pase un puntero nulo).

Esta es la convención utilizada en C cuando una función necesita modificar una variable externa, y la convención utilizada en Java con tipos de referencia: la referencia se copia, pero el objeto referido es el mismo: los cambios en la referencia / puntero no son visibles fuera la función, pero los cambios a la memoria puntiaguda son.

Añadiendo const a la ecuación

En C ++ puede asignar constancia a los objetos al definir variables, punteros y referencias en diferentes niveles. Puede declarar que una variable es constante, puede declarar una referencia a una instancia constante y puede definir todos los punteros a objetos constantes, punteros constantes a objetos mutables y punteros constantes a elementos constantes. Por el contrario, en Java solo puede definir un nivel de constancia (palabra clave final): el de la variable (instancia para tipos primitivos, referencia para tipos de referencia), pero no puede definir una referencia a un elemento inmutable (a menos que la clase misma sea inmutable).

Esto se usa ampliamente en las convenciones de llamadas de C ++. Cuando los objetos son pequeños, puede pasar el objeto por valor. El comstackdor generará una copia, pero esa copia no es una operación costosa. Para cualquier otro tipo, si la función no cambiará el objeto, puede pasar una referencia a una instancia constante (generalmente llamada referencia constante) del tipo. Esto no copiará el objeto, sino que lo pasará a la función. Pero al mismo tiempo, el comstackdor garantizará que el objeto no se modifique dentro de la función.

Reglas de juego

Estas son algunas reglas básicas a seguir:

  • Prefiere pasar por valor para tipos primitivos
  • Prefiere pass-by-reference con referencias a constante para otros tipos
  • Si la función necesita modificar el argumento use pass-by-reference
  • Si el argumento es opcional, use pass-by-pointer (a constante si el valor opcional no debe modificarse)

Existen otras pequeñas desviaciones de estas reglas, la primera de las cuales es manejar la propiedad de un objeto. Cuando un objeto se asigna dinámicamente con nuevo, se debe desasignar con delete (o las [] versiones del mismo). El objeto o función que es responsable de la destrucción del objeto se considera el propietario del recurso. Cuando se crea un objeto dinámicamente asignado en una pieza de código, pero la propiedad se transfiere a un elemento diferente, generalmente se hace con semántica de pase por puntero, o si es posible con punteros inteligentes.

Nota al margen

Es importante insistir en la importancia de la diferencia entre las referencias de C ++ y Java. En C ++, las referencias son conceptualmente la instancia del objeto, no un acceso a ella. El ejemplo más simple es implementar una función de intercambio:

 // C++ class Type; // defined somewhere before, with the appropriate operations void swap( Type & a, Type & b ) { Type tmp = a; a = b; b = tmp; } int main() { Type a, b; Type old_a = a, old_b = b; swap( a, b ); assert( a == old_b ); assert( b == old_a ); } 

La función de intercambio anterior cambia ambos argumentos mediante el uso de referencias. El código más cercano en Java:

 public class C { // ... public static void swap( C a, C b ) { C tmp = a; a = b; b = tmp; } public static void main( String args[] ) { C a = new C(); C b = new C(); C old_a = a; C old_b = b; swap( a, b ); // a and b remain unchanged a==old_a, and b==old_b } } 

La versión de Java del código modificará las copias de las referencias internamente, pero no modificará los objetos reales externamente. Las referencias de Java son punteros en C sin aritmética de puntero que pasan por valor en las funciones.

Hay varios casos para considerar.

Parámetro modificado (parámetros de “salida” y “entrada / salida”)

 void modifies(T &param); // vs void modifies(T *param); 

Este caso se trata principalmente de estilo: ¿quieres que el código se vea como call (obj) o call (& obj) ? Sin embargo, hay dos puntos en los que la diferencia es importante: el caso opcional, a continuación, y desea utilizar una referencia al sobrecargar operadores.

… y opcional

 void modifies(T *param=0); // default value optional, too // vs void modifies(); void modifies(T &param); 

Parámetro no modificado

 void uses(T const &param); // vs void uses(T param); 

Este es el caso interesante. La regla de oro es que los tipos “baratos de copiar” se pasan por valor; estos son generalmente tipos pequeños (pero no siempre), mientras que otros se pasan por const ref. Sin embargo, si necesita hacer una copia dentro de su función independientemente, debe pasar por valor . (Sí, esto expone un poco de detalle de implementación. C’est le C ++. )

… y opcional

 void uses(T const *param=0); // default value optional, too // vs void uses(); void uses(T const &param); // or optional(T param) 

Aquí hay la menor diferencia entre todas las situaciones, así que elige lo que te haga la vida más fácil.

Const por valor es un detalle de implementación

 void f(T); void f(T const); 

¡Estas declaraciones son en realidad la misma función! Al pasar por valor, const es puramente un detalle de implementación. Pruébalo:

 void f(int); void f(int const) { /* implements above function, not an overload */ } typedef void NC(int); // typedefing function types typedef void C(int const); NC *nc = &f; // nc is a function pointer C *c = nc; // C and NC are identical types 

Pase por valor:

 void func (vector v) 

Pase las variables por valor cuando la función necesita un aislamiento completo del entorno, es decir, para evitar que la función modifique la variable original, así como para evitar que otros subprocesos modifiquen su valor mientras se está ejecutando la función.

La desventaja es que los ciclos de la CPU y la memoria extra pasaron para copiar el objeto.

Pase por la referencia de const:

 void func (const vector & v); 

Esta forma emula el comportamiento de paso por valor mientras elimina la sobrecarga de copia. La función obtiene acceso de lectura al objeto original, pero no puede modificar su valor.

La desventaja es la seguridad del hilo: cualquier cambio realizado en el objeto original por otro hilo aparecerá dentro de la función mientras todavía se está ejecutando.

Pase por referencia no const:

 void func (vector & v) 

Úselo cuando la función tenga que devolver algún valor a la variable, que finalmente será utilizada por la persona que llama.

Al igual que el caso de referencia constante, esto no es seguro para subprocesos.

Pase por el puntero de const:

 void func (const vector * vp); 

Funcionalmente lo mismo que pass by const-reference excepto por la syntax diferente, más el hecho de que la función de llamada puede pasar el puntero NULL para indicar que no tiene datos válidos para pasar.

No es seguro.

Pase por puntero no const:

 void func (vector * vp); 

Similar a la referencia no constante. La persona que llama normalmente establece la variable en NULL cuando la función no debe devolver un valor. Esta convención se ve en muchas API glibc. Ejemplo:

 void func (string * str, /* ... */) { if (str != NULL) { *str = some_value; // assign to *str only if it's non-null } } 

Al igual que todos los pasa por referencia / puntero, no es seguro.

Como nadie mencionó que estoy agregando, cuando pasas un objeto a una función en c ++ se llama al constructor de copia predeterminado del objeto si no tienes uno que crea un clon del objeto y luego lo pasa al método, por lo que cuando cambias los valores del objeto que se reflejarán en la copia del objeto en lugar del objeto original, ese es el problema en c ++, entonces si haces que todos los atributos de clase sean punteros, entonces los constructores copiarán las direcciones del atributos de puntero, por lo que cuando las invocaciones de método en el objeto que manipula los valores almacenados en direcciones de atributos de puntero, los cambios también se reflejan en el objeto original que se pasa como un parámetro, por lo que puede comportarse como un Java pero no olvide que toda su clase los atributos deben ser punteros, también debe cambiar los valores de los punteros, será muy claro con la explicación del código.

 Class CPlusPlusJavaFunctionality { public: CPlusPlusJavaFunctionality(){ attribute = new int; *attribute = value; } void setValue(int value){ *attribute = value; } void getValue(){ return *attribute; } ~ CPlusPlusJavaFuncitonality(){ delete(attribute); } private: int *attribute; } void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){ int* prt = obj.attribute; *ptr = value; } int main(){ CPlusPlusJavaFunctionality obj; obj.setValue(10); cout< < obj.getValue(); //output: 10 changeObjectAttribute(obj, 15); cout<< obj.getValue(); //output: 15 } 

Pero esta no es una buena idea, ya que terminará escribiendo mucho código relacionado con punteros, que son propensos a memory leaks y no se olvide de llamar destructores. Y para evitar esto, c ++ tiene constructores de copia donde creará nueva memoria cuando los objetos que contienen punteros pasen a argumentos de función que detendrán la manipulación de otros datos de objetos, Java pasa por valor y el valor es referencia, por lo que no requiere constructores de copia.

Hay tres métodos para pasar un objeto a una función como parámetro:

  1. Pase por referencia
  2. pasar por valor
  3. añadiendo constante en el parámetro

Repase el siguiente ejemplo:

 class Sample { public: int *ptr; int mVar; Sample(int i) { mVar = 4; ptr = new int(i); } ~Sample() { delete ptr; } void PrintVal() { cout < < "The value of the pointer is " << *ptr << endl << "The value of the variable is " << mVar; } }; void SomeFunc(Sample x) { cout << "Say i am in someFunc " << endl; } int main() { Sample s1= 10; SomeFunc(s1); s1.PrintVal(); char ch; cin >> ch; } 

Salida:

Digamos que estoy en algún error
El valor del puntero es -17891602
El valor de la variable es 4

Las siguientes son las maneras de pasar argumentos / parámetros para funcionar en C ++.

1. por valor

 // passing parameters by value . . . void foo(int x) { x = 6; } 

2. por referencia.

 // passing parameters by reference . . . void foo(const int &x) // x is a const reference { x = 6; } // passing parameters by const reference . . . void foo(const int &x) // x is a const reference { x = 6; // compile error: a const reference cannot have its value changed! } 

3. por objeto.

 class abc { display() { cout< <"Class abc"; } } // pass object by value void show(abc S) { cout<