C++ FAQ

Esta es una recopilación de preguntas frecuentes (FAQ por sus siglas en inglés) publicado en C++ Super-FAQ pero no es una traducción oficial ni muy afinada aún. A medida que se avance con la traducción iremos publicando las otras secciones, ahora mismo estamos en la sección de clases y herencia.

Clases y Objetos

Referencias

Funciones en linea

¿Cuál es el problema con las funciones en línea?

Cuando el compilador expande una llamada a una función en línea, el código de la función en línea se inserta en el código de donde es invocada (conceptualmente similar a lo que ocurre con una macro #define). Esto puede, en función de un trillón de otras cosas, mejorar el rendimiento, ya que el optimizador de procedimientos del compilador puede integrar el código de la función en linea, es decir optimizar el código de la función en linea en el segmento de código que lo invoca.

Hay varias maneras para indicar que una función es en línea, algunos casos debe usarse la palabra clave inline, en otros no. No importa lo que usted designe como una función en línea, es una solicitud que el compilador puede irgnorar: podría ampliar algunas, todas o ninguna de las llamadas a una función en línea. (No se desanime, si que parece irremediablemente vago. La flexibilidad de lo anterior es en realidad una gran ventaja: permite al compilador tratar a las funciones grandes de manera diferente que a las pequeñas, además de permite al compilador generar código que sea fácil de depurar, si selecciona las opciones del compilador correctas)

¿Un ejemplo simple de integración de procedimiento?

Considera la siguiente llamada a la función g():

void f()
{
   int x = /*...*/;
   int y = /*...*/;
   int z = /*...*/;
   ...codigo que usa x, y,  z...
   g(x, y, z);
    ...mas codigo que usa x, y,  z...
}

Suponiendo una aplicación típica en C++ que tiene registros y una pila, los registros y parámetros van a la pila justo antes de la llamada a g(), luego los parámetros son leídos de la pila dentro de g() y son leidos de nuevo para restaurar los registros, hasta que g() termine y regrese a f(). Pero eso es mucha lectura y escritura innecesaria, especialmente en los casos en que el compilador es capaz de utilizar los registros de las variables x, y, z: cada variable podría ser puesta dos veces en la pila (como un registro y también como un parámetro) y ser leída dos veces (cuando es utilizado dentro de g() para restaurar los registros mientras regresa a f()).

void g(int x, int y, int z)
{
   ...code that uses x, y and z...
}

Si el compilador expande la llamada a g(), todas las operaciones de memoria podrían desaparecer. Los registros no tendrían que escribirse o leerse, ya que no sería una llamada a la función y los parámetros no se necesitarían para escribirlos o leerlos desde el optimizador por que se sabe que ya están en los registros.

Naturalmente, su kilometraje puede variar, y hay muchas de las variables que están fuera del alcance de este particular FAQ, pero lo anterior es un ejemplo del tipo de cosas que pueden suceder con la integración de procedimientos/funciones.

¿Las funciones en línea mejoran el rendimiento?

Sí y no. A veces. Tal vez.

Las respuestas no son sencillas. las funciones en línea puede hacer más rápido el código, podría hacer más lento el programa. Puede hacer que el ejecutable sea más grande, o más pequeño. Puede causar errores, o impedirlos. Y podría ser totalmente irrelevantes para la velocidad.

Las funciones en línea hacen las cosas más rápidas: Como se ha indicado, la integración de procedimientos podría eliminar un montón de instrucciones innecesarias, lo que podría hacer que las cosas funcionen más rápido.

Las funciones en línea podrían hacer las cosas más lentas: El exceso puede “inflar” el código, lo que podría causar “errores” en los sistemas de memoria virtual paginado bajo demanda. En otras palabras, si el tamaño del ejecutable es demasiado grande, el sistema puede pasar la mayor parte del tiempo en salir del disco y buscar el siguiente segmento de código.

Las funciones en línea podrían hacer las cosas grandes: Esta es la noción de exceso de código, como se describe anteriormente. Por ejemplo, si un sistema dispone de 100 funciones en línea cada uno de los cuales se expande a 100 bytes de código ejecutable y se llama en 100 lugares, eso es un aumento de 1 MB. Es que 1 MB va a causar problemas? Quién sabe, pero es posible que este megabyte retrase las cosas.

Las funciones en línea puede reducir el tamaño: El compilador genera a menudo más código para poner/sacar registros/parámetros de lo que sería el cuerpo de la función en linea. Puede reducir el tamaño del ejecutable con funciones muy pequeñas, y también con las funciones grandes, cuando el optimizador es capaz de eliminar una gran cantidad de código redundante a través de la integración de procedimientos, es decir, cuando el optimizador es capaz de convertir una función grande en pequeña.

Las funciones en línea pueden causar errores: La sentencia inline puede aumentar el tamaño del ejecutable, y podría causar errores.

Las funciones en línea pueden prevenir errores: El espacio de trabajo (número de páginas que deben estar en la memoria a la vez) podría bajar incluso si el tamaño del ejecutable crece. Cuando f() llama g(), el código a menudo esta en dos páginas distintas, cuando el compilador de procedimientos integra el código de g() en f(), el código a menudo esta en la misma página.

Las funciones en línea podrían aumentar el número de fallos en la caché: podría provocar un bucle interno para extenderse a lo largo de varias líneas de la caché de memoria, y podría causar fallos de la memoria caché.

Las funciones en línea podrían disminuir el número de fallos en la caché: suele mejorar las referencias locales en el ejecutable, lo que podría disminuir el número de líneas de caché que se necesita para almacenar el código de un bucle interno. En última instancia, causaría que la aplicación se ejecute más rápido.

Las funciones en línea pueden ser irrelevantes para la velocidad: La mayoría de los sistemas no están enlazados al CPU. La mayoría de los sistemas estan enlazados a entrada y salida/base de datos/red, es decir, el cuello de botella es el sistema de archivos, la base de datos o la red. A menos que el “medidor de CPU” esté vinculado al 100%, las funciones en línea, probablemente no harán que su sistema sea más rápido. (Incluso en los sistemas vinculados a la CPU, le ayudará sólo cuando se usa en el cuello de botella en sí, y el cuello de botella suele estar en sólo un pequeño porcentaje del código.)

Las respuestas no son sencillas: Tienes que jugar con ellas para ver cual se ajusta a lo estas buscando. No se conforme con respuestas simplistas como: “Nunca usar las funciones en línea” o “Utilizar siempre las funciones en línea” o “Uso de las funciones en línea si y sólo si la función es menor que N líneas de código.” Estas reglas pueden ser fáciles de escribir, pero no van a producir resultados óptimos.

¿Cómo ayudan las funciones en línea con el equilibrio seguridad vs velocidad?

En C directamente, se puede lograr “estructuras encapsuladas” poniendo un void* en una estructura, en cuyo caso los punteros void* apuntan a los datos reales que se son desconocidos por los usuarios de la estructura. Por lo tanto los usuarios de la estructura no saben cómo interpretar a las cosas que apunta el void*, pero las funciones que tienen acceso hacen un cast al void* hacia el tipo de dato (oculto) apropiado. Esto le da una forma de encapsulación.

Por desgracia, se pierde la seguridad de tipos, y también impone una llamada de función para acceder incluso a campos triviales de la estructura (si permite el acceso directo a los campos de la estructura, cualquiera y todo el mundo sería capaz de obtener acceso directo a ellos, ya que solo sería necesario saber como interpretar el apuntador void*, lo que haría difícil cambiar la estructura de datos base).

Las sobrecargas de funciones son pequeñas, pero pueden crecer. Clases C++ permiten llamadas a funciones que se expanden en línea. Esto le permite tener la seguridad de encapsulación, junto con la velocidad de acceso directo. Además los tipos de parámetros de estas funciones en línea son verificados por el compilador, una mejora con respecto a la macro #define.

hay que afinar la traduccion parashift.com/c++-faq-lite/inline-functions.html#faq-9.4

¿Porqué debería usar inline en lugar del viejo #define?

Por que la macro #define es terrible por 4 razones (terrible razon #1, terrible razon #2 terrible razon #3, terrible razon #4 faqs no traucidos aun). Algunas veces, usted debe utilizarla de todos modos, pero siguen siendo terribles.

A diferencia de la macro #define, las funciones inline evitan infames errores de estas macros, ya que las funciones en línea siempre se evalúan todos los argumentos exactamente una vez. En otras palabras, la invocación de una función en línea es semánticamente al igual que la invocación de una función regular, pero más rápido:

// una macro que devuelve el valor absoluto de i
 #define inseguro(i)  \
         ( (i) >= 0 ? (i) : -(i) )
 
 // una funcion inline que devuelve el valor absoluto de i
 inline int seguro(int i)
 {
   return i >= 0 ? i : -i;
 }
 
 int f();
 
 void algunaFuncion(int x)
 {
   int ans;

   ans = inseguro(x++);   // Horror! x es incrementada dos veces
   ans = inseguro(f());   // Peligroso! f() es llamada dos veces
 
   ans = seguro(x++);     // Correcto! x es incrementado una sola vez
   ans = seguro(f());     // Correcto! f() es llamado una sola vez
 }

También a diferencia de las macros, los tipos de dato de los argumentos se comprueban, y las conversiones necesarias se realizan correctamente.

Las macros son malas para su salud, no los use a menos que tenga que hacerlo.

¿Cómo declaro una función inline fuera de una clase?

Cuando se declara una función en línea, se ve como una función normal:

 void f(int i, char c);

Pero cuando defina una función en línea, debe anteponer, a la definición de la función, la palabra clave inline, y seguidamente la misma definición del archivo de cabecera:

 inline void f(int i, char c)
 {
   ...
 }

Nota: Es imprescindible que la definición de la función (la parte entre los {…}) sean colocados en el archivo de cabecera, a menos que la función utiliza sólo un archivo cpp… En particular, si usted pone la definición de la función en línea en un archivo .cpp y lo llama de algun otro .cpp, obtendrá un error del vinculador “externo sin resolver” (unresolved external).

¿Cómo declaro una función inline dentro de una clase?

Cuando se declara una función en línea dentro de una clase, se ve como una función miembro normal:

 class Fred {
 public:
   void f(int i, char c);
 };

Pero cuando se define la función miembro, que antepone la definición de la función del usuario en línea con la palabra clave, y puso la definición en un archivo de encabezado:

Pero cuando defina una función en línea, debe anteponer, a la definición de la función, la palabra clave inline, y seguidamente la misma definición del archivo encabezado:

 inline void Fred::f(int i, char c)
 {
   ...
 }

Es usualmente imperativo que la definición de la función (la parte entre los {…}) sean colocados en el archivo de cabecera. En particular, si usted pone la definición de la función en línea en un archivo .cpp y lo llama de algun otro .cpp, obtendrá un error del vinculador “externo sin resolver” (unresolved external).

¿Hay otra manera declarar una función miembro inline?

Claro que sí: defina la función miembro en el cuerpo de la clase:

class Fred {
 public:
   void f(int i, char c)
     {
       ...
     }
 };

Aunque esto es más fácil a la persona que escribe la clase, es más difícil para el resto de lectores ya que mezcla el “que” con el “cómo” lo hace. Debido a esta mezcla, es que normalmente prefieren definir las funciones miembro fuera del cuerpo de la clase con la palabra clave en línea. La idea que da sentido a esto: en un mundo orientado a la reutilización, donde mucha gente que utilizará tu clase, pero hay sólo una persona que la puede compilar (usted), por lo que debe hacer las cosas en favor del resto de usuarios de su clase más que en unos pocos. Este enfoque es más desarrollado en la siguiente pregunta.[ref]( que aun no ha sido traducida)[/ref]

Con las funciones inline que son definidas fuera de la clase: ¿es mejor poner la palabra clave inline seguido de la declaración entro del cuerpo de la clase o seguido de la definición fuera de la clase o ambos?

Practica recomendada: sólo en la definición, fuera del cuerpo de la clase.

 class Algo {
 public:
   void metodo();  ← practica recomendada: no poner la palabra clave inline
   ...
 };
 
inline void Algo::metodo()  ← practica recomendada: ponga la palabra clave inline aqui.
{ ... }

Ésta es la idea básica:

  • La sección public: del cuerpo de la clase es donde se describe la semántica observable de una clase como las funciones/métodos públicos, funciones amigas, y todo lo demás exportado por la clase. Trate de no proporcionar ningún indicio de algo que no puede ser observada desde el código de quien llama a las funciones de la clase.
  • Las otras secciones de la clase, incluidos los no públicos: parte del cuerpo de la clase, las definiciones de sus miembros y las funciones amigas, etc son mera implementación. Trate de no describir cualquier semántica observable que no se han descrito ya en la sección publica de la clase.

Desde un punto de vista práctico, esta separación que hace la vida más fácil y segura para sus usuarios. Diga Chuck quiere simplemente “usar” su clase. Debido a que usted ha leído esta FAQ y utiliza la separación anterior, Chuck puede leer su clase de la sección public: y ver todo lo que tiene que ver y nada de lo que no tiene que ver. Su vida es más fácil porque tiene que buscar en un solo lugar, y más seguro también porque su mente pura no está contaminada por pequeños detalles de implementación.

Volver a esto de funciones en línea: la decisión de si una función está o no está en línea es un detalle de implementación que no cambia la semántica observable (el “significado”) de una función. Por lo tanto la palabra clave inline debe ir junto a la definición de la función, no en la parte public: de la clase.

NOTA: la mayoría de la gente usa los términos “declaración” y “definición” para diferenciar estos dos lugares. Por ejemplo, se podría decir: “¿Debo poner la palabra clave inline junto a la declaración o la definición?” Por desgracia, eso es un uso descuidado y alguien por ahí eventualmente te harán trabajar por ello. La gente que te haga trabajar probablemente son inseguros patéticos aspirantes que saben que no son lo suficientemente bueno para llevar a cabo realmente algo con sus vidas, sin embargo, ellos también podrían aprender la terminología correcta para evitar trabajar en vano. Aquí está: cada definición es también una declaración. Esto significa utilizar los dos como si fueran mutuamente excluyentes sería como preguntar que es más pesado, de acero o de metal? Casi todo el mundo sabe lo que quiere decir si se utiliza la “definición” como si fuera lo contrario de “declaración”, y sólo la peor de las personas necesitarán aclaración, pero al menos sabrán cómo usar los términos correctamente.

C++  

Dejar una Respuesta