Guía C++
Guía C++
Guía C++
Esta es una guía de estilo para programar en C++. En una traducción adaptada de la guía de estilo del
Curso CS 106B: Programming Abstractions (C++) de los estudios de Computer Science de la Stanford
University (https://stanford.edu/class/archive/cs/cs106b/cs106b.1158/styleguide.shtml). En este
documento se han respetado buena parte de los ejemplos ilustrativos contenidos de la guía original.
Esta guía contiene una colección de reglas de estilo que conviene tener en cuenta al escribir código C++.
La calificación de los trabajos y exámenes de las asignaturas que recomienden el seguimiento de esta
guía se verá condicionada por esta circunstancia.
El objetivo de las reglas de estilo es favorecer la calidad del código diseñado y su legibilidad. Esta
colección no es exhaustiva y podrá verse enriquecida con nuevas reglas en un futuro. Las reglas tampoco
son totalmente rígidas. Su aplicación debe estar condicionada por el sentido común y las circunstancias
del programa que se está escribiendo.
En la mayoría de los entornos de trabajo profesional se sigue un estándar de estilo. Aprender a obedecer
cuidadosamente una guía de estilo y escribir código en equipo, junto con otros desarrolladores que
obedecen el mismo estándar de estilo, constituye una excelente práctica profesional que conviene
ejercitar desde la Universidad.
Sangrado: Incrementar en una unidad el nivel de sangrado después de cada llave izquierda, {, y
disminuirlo en una unidad después de cada llave derecha, }. Un nivel de sangrado puede constar de 3 ó 4
caracteres o de un carácter tabulador.
// mal
int x = 3, y = 7; double z = 4.25; x++;
if (a == b) { foo(); }
// bien
int x = 3;
int y = 7;
double z = 4.25;
x++;
if (a == b) {
foo();
}
Longitud de las líneas: Ninguna línea debe superar los 100 caracteres. En tal caso la línea debe
descomponerse en dos o más y la segunda y, en su caso, las líneas sucesivas deben estar sangradas dos
niveles respecto a la primera. Por ejemplo:
Expresiones: Se debe escribir un espacio en blanco entre cada operador y sus operandos.
int x = (a + b) * c / d + foo();
Líneas en blanco: Escribir una línea en blanco de separación entre dos funciones consecutivas.
void foo() {
...
}
// aquí hay una línea en blanco
void bar() {
...
}
2. Identificadores y variables
Identificadores: Deben darse a las variables nombres significativos, tales como firstName o
homeworkScore. Evitar identificadores de una sola letra tales como x o c, excepto para variables que
sean índices de bucles tales como i.
Mayúsculas: Los nombres de variables y de funciones han de comenzar con una letra minúscula,
likeThis, los nombres de clases con una letra mayúscula, LikeThis, y los nombres de constantes se
escriben todo en mayúsculas, sin ninguna minúscula, LIKE_THIS.
Ámbito: Cada variable hay que declararla en el ámbito más restringido posible. Si, por ejemplo, una
variable solo es usada dentro de una determinada instrucción if o while hay que declararla dentro de
dicha instrucción if o while y no al comienzo del código de la función o al comienzo del fichero.
Tipos: Elegir el tipo de dato apropiado para cada variable. Si una variable solo ha de almacenar valores
enteros, hay que declararla de tipo int y no de tipo double.
// mal: estilo C
char* str = "Hello there";
// bien: estilo C++
char str[] = "Hello there";
Evitar variables globales: No declarar nunca variables globales. Los únicos datos globales que se
pueden definir son los de ciertas constantes const. En vez de utilizar datos globales para comunicar
información entre funciones, se ha de pasar dicha información a través de sus parámetros y de los
valores que devuelven al ser invocadas.
// mal
int count; // variable global; mal!
void func1() {
count = 42;
}
void func2() {
count++;
}
int main() {
func1();
func2();
}
// mucho mejor
int func1() {
return 42;
}
int main() {
int count = func1();
func2(count);
}
Priorizar el estilo C++ sobre el estilo C: Aunque C++ está basado en C, puede hablarse de un "estilo
C++ " y de un "estilo C" al utilizar determinadas construcciones. Por ejemplo, para escribir por la
consola del sistema, el "estilo C++ " hace uso del flujo de salida cout, mientras que el "estilo C " invoca
funciones tales como printf. Deberemos hacer uso del estilo "C++” siempre que sea posible.
// mal
printf("Hello, world!\n");
// bien
cout << "Hello, world!" << endl;
for vs while: Programaremos un bucle for cuando el número de repeticiones sea conocido a priori y
programaremos un bucle while cuando el número de repeticiones sea desconocido a priori.
break and continue: Evitaremos utilizar las instrucciones break or continue en bucles, excepto en los
casos en que ello sea imprescindible.
exit(0): Nunca haremos uso en nuestros programas de la función exit(0) que da por concluida de
forma inmediata la ejecución de un programa C++. Nuestros programas siempre concluirán su ejecución
al alcanzar el final de su función main.
// mal
if (size == 0) return 0;
else
for (int i = 0; i < 10; i++) cout << "ok" << endl;
// bien
if (size == 0) {
return 0;
}
else {
for (int i = 0; i < 10; i++) {
cout << "ok" << endl;
}
}
Condiciones de una instrucción if/else: Debemos evitar una secuencia de instrucciones condicionales
if/else innecesarias y sustituirla por una única instrucción if/else. De este modo reduciremos el
número de condiciones a evaluar.
Evitar cláusulas sin código en una instrucción if/else: Debemos evitar que la cualquiera de las
cláusulas de una instrucción if/else esté vacía de código a ejecutar.
// mal
if (a >= 0) {
// no hacer nada
}
else {
a = -a;
}
...
// bien
if (a < 0) {
a = -a;
}
...
Cálculo zen de valores booleanos (1): Debe evitarse programar una instrucción condicional if/else
que devuelva o asigne a una variable un valor de tipo bool en función del resultado de una determinada
condición. En tal caso se debe programar directamente la devolución o asignación del valor resultante de
evaluar la condición.
// mal
if (score1 == score2) {
return true; // Asignación de valor: v = true;
}
else {
return false; // Asignación de valor: v = false;
}
// bien
return score1 == score2; // Asignación de valor: v = score1 == score2;
Cálculo zen de valores booleanos (2): No escribir nunca una expresión booleana cuyo resultado sea
igual (==) o distinto (!=) a true o false.
// mal
if (x == true) {
...
}
else if (x != true) {
...
}
// bien
if (x) {
...
}
else {
...
}
4. Redundancias
Minimizar el código redundante: Si escribes el mismo código dos o más veces debes encontrar el
modo de que el código solo aparezca una vez y sea eliminado el código redundante. Se puede lograr, por
ejemplo, escribiéndolo en una función auxiliar que sea invocada cuantas veces sea necesario. Si el
código repetido es muy semejante, pero no idéntico, se puede intentar diseñar una función auxiliar con
parámetros que acepten los datos diferentes de cada porción de código repetida.
// mal
int aux = x;
x = y;
y = aux;
foo();
...
x = 10;
y++;
...
int temp = a;
a = b;
b = temp;
foo();
...
// bien
helper(x, y);
...
helper(a, b);
...
Factorización de una instrucción if/else: Reubicar el código común de las diferentes cláusulas de
una instrucción if/else de forma que se evite su repetición.
// mal
if (x < y) {
foo();
x++;
cout << "hi";
}
else {
foo();
y++;
cout << "hi";
}
// bien
foo();
if (x < y) {
x++;
}
else {
y++;
}
cout << "hi";
Estructura de las funciones: Una función cuyo código fuera a resultar muy largo, conviene
descomponerla en varias subfunciones. Aunque la definición de "muy largo " es imprecisa, diremos que
una función de más de 40 ó 50 líneas ya lo es. Estas 40 o 50 líneas constituyen un límite superior. No
obstante, si al especificar una función hay que utilizar muchas veces la conjunción “y” al describir sus
objetivos, es muy posible que la función deba hacer demasiadas cosas y convenga descomponerla en
subfunciones.
5. Eficiencia
// mal
if (reallySlowSearchForIndex("abc") >= 0) {
remove(reallySlowSearchForIndex("abc"));
}
// bien
int index = reallySlowSearchForIndex("abc");
if (index >= 0) {
remove(index);
}
6. Documentación y comentarios
Citar fuentes: Si se han utilizado fuentes externas en el diseño de un programa (un libro, una página
web, otro programa, etc.), se deben citar dichas fuentes en el comentario del comienzo del fichero. Es
importante citar todas las fuentes relevantes utilizadas y reconocer el trabajo de sus correspondientes
autores.
Comentarios en el código de una función. Si el código de una función presenta secciones largas,
complejas o no triviales, conviene incluir en estos puntos algún comentario que ayude a comprender qué
hace esa sección de código.
Buen diseño de una función: Un función bien diseñada presenta propiedades tales como las siguientes:
Parámetros por valor vs parámetros por referencia: Los parámetros por referencia se han de utilizar
en los siguientes casos:
siempre que sean parámetros de ‘salida’, es decir, cuando representen resultados de la función y
cuando la función deba modificar el valor de la variable asociada al parámetro y
cuando la función tenga que devolver varios resultados
No se deben definir parámetros por referencia cuando ello no sea necesario ni beneficioso. En el ejemplo
que sigue a, b, y c no son parámetros por referencia porque no es necesario que lo sean.
/*
* Solves a quadratic equation ax^2 + bx + c = 0,
* storing the results in output parameters root1 and root2.
* Assumes that the given equation has two real roots.
*/
void quadratic(double a, double b, double c,
double& root1, double& root2) {
double d = sqrt(b * b - 4 * a * c);
root1 = (-b + d) / (2 * a);
root2 = (-b - d) / (2 * a);
}
// mal
void max (int a, int b, int& result) {
if (a > b) {
result = a;
}
else {
result = b;
}
}
// bien
int max (int a, int b) {
if (a > b) {
return a;
}
else {
return b;
}
}
Paso de datos estructurados y de objetos por referencia: Cuando se transmite un dato estructurado
(definido como un registro struct) o un objeto no conviene hacerlo mediante un parámetro por valor,
sino mediante un parámetro por referencia, para evitar que el dato estructurado o el objeto deba ser
copiado en la función, lo cual resulta ineficiente:
// mal
void process(BankAccount account) {
...
}
// bien
void process(BankAccount& account) {
...
}
Parámetros constantes (const) por referencia: Si hay que pasarle un dato estructurado (o un
objeto) a una función y su código no va a modificar su valor (su estado), entonces se recomienda pasarlo
como un parámetro constante (const) por referencia.
// mal
void display(BankAccount& account) {
...
}
// bien
void display(const BankAccount& account) {
...
}
Parámetros por referencias vs. punteros: Aunque se conozca el uso de punteros en C/C++, se
recomienda priorizar la definición de parámetros por referencia en lugar de definirlos como punteros.
Una razón de ello es porque un parámetro por referencia, a diferencia de un puntero, no puede tener el
valor NULL.
// mal
// accepts a pointer to an account
void process(BankAccount* account) {
...
}
// bien
// accepts a reference to an account
void process(BankAccount& account) {
...
}
Evitar llamadas ‘encadenadas’ donde cada función invocan a otra en una larga cadena sin devolver el
control de la ejecución a la función main. Conviene asegurarse que la función main es un resumen
conciso de la estructura global del programa. Esta idea se ilustra con los dos esquemas de invocaciones
que se presentan a continuación. En el primero cada función invoca a una función auxiliar antes de
finalizar su ejecución, mientras que en el segundo esquema se limita el ‘encadenamiento’ de llamadas
anidadas.
// bad
main
|
+-- function1
|
+-- function2
|
+-- function3
|
+-- function4
|
+-- function5
|
+-- function6
// good
main
|
+-- function1
|
+-- function2
| |
| +-- function3
| |
| +-- function4
|
+-- function5
| |
| +-- function6
8. Diseño de clases
Encapsulación: Deben encapsularse correctamente los objetos haciendo que cualquier campo de datos
de su clase sea definido en la sección private.
class Student {
private:
int homeworkScore;
...
Ficheros .h vs ficheros .cpp: Escribir siempre la declaración de una clase y de sus funciones en su
correspondiente fichero ClassName.h. Escribir la implementación de sus funciones en su
correspondiente fichero ClassName.cpp. Escribir siempre la declaración de una clase en su fichero .h
dentro de un bloque #ifndef/define/endif para evitar múltiples declaraciones de la misma clase en
un programa.
// Point.h
#ifndef _point_h
#define _point_h
class Point {
public:
Point (int x, int y);
int getX() const;
int getY() const;
void translate(int dx, int dy);
private:
int m_x;
int m_y;
};
#endif
// Point.cpp
#include "Point.h"
Point::Point(int x, int y) {
m_x = x;
m_y = y;
}
Evitar atributos inecesarios; se debe hacer uso de los atributos de un objeto para almacenar datos del
estado del objeto pero no para almacenar valores temporales como, por ejemplo, los utilizados
únicamente en una única llamada a una función.
Funciones auxiliares: Cualquier función adicional de una clase que no forme parte de su especificación
se definirá como private para ocultarla y para que no pueda ser invocada desde una porción de código
exterior a la propia clase.
class Student {
...
private:
double computeTuitionHelper();
Funciones const: Una función de clase que no modifica el estado del objeto sobre el que es invocada
se ha de declarar como const.
class Student {
public:
int getID() const;
double getGPA(int year) const;
void payTuition(Course& course);
string toString() const;
...