Disponible la versión 6 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


1.2  El lenguaje C++

"Learn not just the hows, but the whys too".  Greg Comeau [7].  

§1  Generalidades

C++ es un lenguaje imperativo orientado a objetos derivado del C [1].  En realidad un superconjunto de C, que nació para añadirle cualidades y características de las que carecía.  El resultado es que como su ancestro, sigue muy ligado al hardware subyacente, manteniendo una considerable potencia para programación a bajo nivel, pero se la han añadido elementos que le permiten también un estilo de programación con alto nivel de abstracción.

Nota: estrictamente hablando, C no es un subconjunto de C++;  de hecho es posible escribir código C que es ilegal en C++.  Pero a efectos prácticos, dado el esfuerzo de compatibilidad desplegado en su diseño, puede considerarse que C++ es una extensión del C clásico.  La definición "oficial" del lenguaje nos dice que C++ es un lenguaje de propósito general basado en el C, al que se han añadido nuevos tipos de datos, clases, plantillas, mecanismo de excepciones, sistema de espacios de nombres, funciones inline, sobrecarga de operadores, referencias, operadores para manejo de memoria persistente, y algunas utilidades adicionales de librería  (en realidad la librería Estándar C es un subconjunto de la librería C++).

Respecto a su antecesor, se ha procurando mantener una exquisita compatibilidad hacia atrás por dos razones [2]:  poder reutilizar la enorme cantidad de código C existente, y facilitar una transición lo más fluida posible a los programadores de C clásico, de forma que pudieran pasar sus programas a C++ e ir modificándolos (haciéndolos más "++") de forma gradual. De hecho, los primeros compiladores C++ lo que hacían en realidad era traducir (preprocesar) a C y compilar después [3] (las consecuencias se dejan sentir todavía en el lenguaje 1.4.2).

Por lo general puede compilarse un programa C bajo C++, pero no a la inversa si el programa utiliza alguna de las características especiales de C++.  Algunas situaciones requieren especial cuidado. Por ejemplo, si se declara una función dos veces con diferente tipo de argumentos, el compilador C invoca un error de "Nombre duplicado", mientras que en C++ quizás sea interpretado como una sobrecarga de la primera función (que sea o no legal depende de otras circunstancias).

Como se ha señalado, C++ no es un lenguaje orientado a objetos puro (en el sentido en que puede serlo Java por ejemplo), además no nació como un ejercicio académico de diseño.  Se trata simplemente del sucesor de un lenguaje de programación hecho por programadores (de alto nivel) para programadores, lo que se traduce en un diseño pragmático al que se le han ido añadiendo todos los elementos que la práctica aconsejaba como necesarios, con independencia de su belleza o purismo conceptual ("Perfection, in some language theoretical sense, is not an aim of C++. Utility is" [6]).  Estos condicionantes tienen su cara y su cruz;  en ocasiones son motivo de ciertos "reproches" por parte de sus detractores, en otras, estas características son precisamente una cualidad.  De hecho, en el diseño de la Librería Estándar C++ ( 5.1) se ha usado ampliamente esta dualidad (ser mezcla de un lenguaje tradicional con elementos de POO), lo que ha permitido un modelo muy avanzado de programación extraordinariamente flexible (programación genérica).

Aunque C++ introduce nuevas palabras clave y operadores para manejo de clases, algunas de sus extensiones tienen aplicación fuera del contexto de programación con objetos (fuera del ámbito de las clases), de hecho, muchos aspectos de C++ que pueden ser usados independientemente de las clases [5].


Del C se ha dicho: "Por naturaleza, el lenguaje C es permisivo e intenta hacer algo razonable con lo que se haya escrito. Aunque normalmente esto es una virtud, también puede hacer que ciertos errores sean difíciles de descubrir" ( Shildt).  Respecto al C++ podríamos decir otro tanto, pero hemos de reconocer que su sistema de detección de errores es mucho más robusto que el de C, por lo que algunos errores de este serán rápidamente detectados.

Desde luego, C++ es un lenguaje de programación extremadamente largo y complejo;  cuando nos adentramos en él parece no acabar nunca.  Justo cuando aprendemos un significado descubrimos que una mano negra ha añadido otras dos o tres acepciones para la misma palabra.  También descubrimos que prácticamente no hay una regla sin su correspondiente excepción.  Cuando aprendemos que algo no se puede hacer, hay siempre algún truco escondido para hacerlo, y cuando nos dicen que es un lenguaje fuertemente tipado ("Strong type checking"), resulta completamente falso.

A pesar de todo, ha experimentado un extraordinario éxito desde su creación.  De hecho, muchos sistemas operativos [4], compiladores e intérpretes han sido escritos en C++ (el propio Windows y Java).  Una de las razones de su éxito es ser un lenguaje de propósito general que se adapta a múltiples situaciones.  Para comprobar el éxito e importancia de los desarrollos realizados en C++ puede darse una vuelta por la página que mantiene el Dr. Stroustrup al respecto:    www.research.att.com.

Tanto sus fervientes defensores como sus acérrimos detractores han hecho correr ríos de tinta ensalzando sus cualidades o subrayando sus miserias, aunque todo el mundo parece estar de acuerdo en que es largo y complejo.  Ha servido de justificación para el diseño de otros lenguajes que intentan eliminar sus inconvenientes al tiempo que mantener sus virtudes (C# y Java por ejemplo), y una de sus última incorporaciones, las plantillas ( 4.12), ha sido origen de un nuevo paradigma de programación (metaprogramación).

En mi opinión, cualquier lenguaje de propósito general que como C++, permita tocar ambos mundos, la programación de bajo nivel y altos niveles de abstracción, resultará siempre e inevitablemente complejo.  Ocurre lo mismo con los lenguajes naturales que son también extraordinariamente complejos (esto lo saben bien los gramáticos).  Cualquier comunicación entre humanos presupone una ingente cantidad de conocimientos y suposiciones previas entre los interlocutores.  A pesar de lo cual, la comunicación exacta y sin ambigüedades entre dos personas no resulta fácil.

§2 Consejos para mejorar el rendimiento

Lo mismo que en su ancestro, en el diseño del C++ primó sobre todo la velocidad de ejecución del código [4].  Tanto uno como otro representan los ejecutables más rápidos que se pueden construir para una máquina y circunstancias determinadas.  En este sentido, la única alternativa de mejora es la codificación manual, el "pulido" de determinadas rutinas (o de todo el código) en ensamblador,  aunque  evidentemente esto es impracticable para aplicaciones medianamente grandes, a no ser que se disponga de todos los recursos y tiempo del mundo.

Con todo, a pesar de ser un lenguaje intrínsecamente rápido, y de que los compiladores modernos son bastante "inteligentes" en este sentido (adoptan automáticamente las decisiones que resultan en el código de ejecución más eficiente), es mucho lo que puede hacer el programador para favorecer esta rapidez con solo adoptar algunas sencillas precauciones.  Estos son los consejos:

  • Use enteros (int) con preferencia sobre cualquier otro tipo de variable numérica.  En especial en los contadores de bucles.  Las operaciones con enteros son del orden de 10 a 20 veces más rápidas que las de números en coma flotante.

  • Use operadores incremento y decremento ++/-- ( 4.9.1)

  • Use variables de registro, en especial en los bucles críticos, sobre todo si son anidados ( 4.1.8b).

  • Use aritmética de punteros frente a subíndices de matrices ( 4.2.2).

  • En problemas de computación numérica recuerde que el cálculo de funciones trascendentes es por lo general muy lento.

  • Use referencias para argumentos y valores devueltos en funciones, antes que objetos "por valor" ( 4.2.3)

  • Al definir clases utilice al mínimo las funciones virtuales ( 1.4.4 4.11.8a), así como los punteros a funciones-miembro ( 4.2.1g)

    • Tenga en cuenta lo señalado respecto al rendimiento al tratar de:

    • Sustituciones inline en funciones definidas por el usuario ( 4.4.6b)

  • Preste atención al modo de uso de aquellas funciones de librería que se presentan en dos versiones ( 5.1 Funciones y macros)

Los compiladores modernos permiten fijar que criterio de optimización será dominante:  La velocidad de ejecución o el tamaño.  Tanto Borland C++ ( 1.4.3) como MS Visual C++ utilizan la misma convención de llamada para este propósito (opciones -O2 o -O1 respectivamente). Por su parte, GNU gcc dispone de varias opciones de optimización.  En particular, la opción -Os adopta las medidas tendentes a reducir el tamaño del código resultante.

Nota:   Aparte de las decisiones de optimización que puedan adoptar automáticamente los compiladores y las reglas de precaución anteriores, las modernas "suites" ofrecen herramientas de análisis que permiten conocer de forma objetiva como es la utilización de recursos dentro del programa, de forma que se puedan adoptar precauciones en las zonas que resulten más costosas, y concentrar nuestros esfuerzos de optimización en la zonas donde resulten más provechosos.  El compilador GNU cpp dispone de la utilidad gcov, que ofrece estadísticas para analizar el rendimiento del código. Entre otros datos:

  • Cómo se ejecuta cada línea de código
  • Qué línea se está ejecutando actualmente.
  • Cuanto tiempo consume cada sección de código
  • Posibilidad de análisis a nivel de fichero o de función

  Inicio.


[1]  Los lenguajes imperativos son aquellos en los que se especifica cómo conseguir los objetivos que se persiguen. C; C++; Javascript y Perl, entre otros muchos, pertenecen a esta categoría. Por contra, los lenguajes declarativos son aquellos en los que se especifica que objetivo se persigue, sin preocuparse por el cómo. SQL y HTML son quizás los ejemplos más representativos de esta categoría.

[2]  El propio Stroustrup reconoce en el prólogo de su primer libro sobre C++: "Se ha dado gran importancia a mantener compatibilidad con C .... C++ debe la mayor parte a C. C es mantenido como un subconjunto" ( Stroustrup 1987).

[3]  De hecho, el mentado compilador Zortech C++, se ofrecía como: "The World's first 'TRUE' C++ compiler for MS-DOS".

[4]  Stroustrup señala que una de las razones que llevaron a K&R a diseñar el C fue no tener que programar en ensamblador (*), y que en el diseño de C++ se cuidó no perder las ventajas ganadas en este sentido.  Precisamente comenzó a desarrollar C++ como un lenguaje que le ayudara en el trabajo de diseño de un Sistema Operativo distribuido.

(*) En ocasiones, C ha sido considerado como un "lenguaje ensamblador de alto nivel".

[5]  "You are not doing OO just because you are compiling with C++"  Ian Joyner "C++??" ( 7).

[6]  Bjarne Stroustrup.  Entrevista con Al Stevens en Dr. Dobb's Journal Sept. 1992.

[7]  Entrevista en "C++ Pointers and Dynamic Memory Management" de Michael C. Daconta.  Edit. John Wiley & Sons, Inc. ISBN 0-471-04998-0