Este apéndice es una colección de sugerencias para programación con C++. Se han reunido a lo largo de mi experiencia en como docente y programador y
también de las aportaciones de amigos incluyendo a Dan Saks (co-autor junto a Tom Plum de C++ Programming Guidelines, Plum Hall, 1991), Scott Meyers (autor de Effective C++, 2ª edición, Addison-Wesley, 1998), and Rob Murray (autor de C++ Strategies & Tactics, Addison-Wesley, 1993). También, muchos de los consejos están resumidos a partir del contenido de Thinking in C++.
Primero haga que funcione, después hágalo rápido. Esto es cierto incluso si se está seguro de que una trozo de código es realmente importante y se sabe que será un cuello de botella es el sistema. No lo haga. Primero, consiga que el sistema tenga un diseño lo más simple posible. Entonces, si no es suficientemente rápido, optimícelo. Casi siempre descubrirá que «su» cuello de botella no es el problema. Guarde su tiempo para lo verdaderamente importante.
La elegancia siempre vale la pena. No es un pasatiempo frívolo. No sólo permite que un programa sea más fácil de construir y depurar, también es más fácil de comprender y mantener, y ahí es donde radica su valor económico. Esta cuestión puede requerir de alguna experiencia para creerselo, porque puede parecer que mientras se está haciendo un trozo de código elegante, no se es productivo. La productividad aparece cuando el código se integra sin problemas en el sistema, e incluso cuando se modifica el código o el sistema.
Recuerde el principio «divide y vencerás». Si el problema al que se enfrenta es desmasiado confuso, intente imaginar la operación básica del programa se puede hacer, debido a la existencia de una «pieza» mágica que hace el trabajo difícil. Esta «pieza» es un objeto - escriba el código que usa el objeto, después implemente ese objeto encapsulando las partes difíciles en otros objetos, etc.
No reescriba automáticamente todo su código C a C++ a menos que necesite un cambiar significativamente su funcionalidad (es decir, no lo arregle si no está roto). Recompilar C en C++ es un positivo porque puede revelar errores ocultos. Sim embargo, tomar código C que funciona bien y reescribirlo en C++ no es la mejor forma de invertir el tiempo, a menos que la versión C++ le ofrezca más oportunidad de reutilizarlo como una clase.
Si tiene un gran trozo de código C que necesite cambios, primero aisle las partes del código que no se modificará, posiblemente envolviendo esas funciones en una «clase API» como métodos estáticos. Después ponga aténción al código que va a cambiar, recolocandolo dentro de clases para facilitar las modificaciones en el proceso de mantenimiento.
Separe al creador de la clase del usuario de la clase (el programador cliente). El usuario de la clase es el «consumidor» y no necesita o no quiere conocer que hay dentro de la clase. El creador de la clase debe ser un experto en diseño de clases y escribir la clase para que pueda ser usada por el programador más inexperto posible, y aún así funcionar de forma robusta en la aplicación. El uso de la librería será sencillo sólo is es transparente.
Cuando cree una clase, utilice nombres tan claros como sea posible. Eo objetivo debería ser que la interface del programador cliente sea conceptualmente simple. Intente utilizar nombres tan claros que los comentarios sean innecesarios. Luego, use sobrecarga de funciones y argumentos por defecto para crear un interface intuitiva y fácil de usar.
El control de acceso permite (al creador de la clase) cambiar tanto como sea posible en el futuro sin afectar al código del cliente en el que se usa la clase. FIXME:Is this light, mantenga todo tan privado como sea posible, y haga pública solamente la interfaz de la clase, usando siempre métodos en lugar de atributos. Ponga atributos públicos sólo cuando se vea obligado. Si una parte de su clase debe quedar expuesta a clases derivadas como protegida, proporcione una interface con funciones en lugar de exponer los datos reales. De este modo, los cambios en la implementación tendrán un impacto mínimo en las clases derivadas.
FIXME No caiga en FIXME:analysis paralysis. Hay algunas cosas que no aprenderá hasta que empiece a codificar y consiga algún tipo de sistema. C++ tiene mecanimos de seguridad de fábrica, dejelos trabajar por usted. Sus errores en una clase o conjunto de clases no destruirá la integridad del sistema completo.
El análisis y diseño debe producir, como mínimo, las clases del sistema, sus interfaces públicas, y las relaciones con otras clases, especialmente las clases base. Si su metodología de diseño produce más que eso, preguntese a si mismo si todas las piezas producidas por la metodología tiene valor respecto al tiempo de vide del programa. Si no lo tienen, no mantenga nada que no contribuya a su productividad, este es un FIXME:fact of life] que muchos métodos de diseño no tienen en cuenta.
Escriba primero el código de las pruebas (antes de escribir la
clase), y guardelo junto a la clase. Automatice la ejecución de
las pruebas con un makefile
o herramienta
similar. De este modo, cualquier cambio se puede verificar
automáticamente ejecutando el código de prueba, lo que permite
descubrir los errores inmediatamante. Como sabe que cuenta con
esa red de seguridad, puede arriesgar haciendo cambios más
grandes cuando descubra la necesidad. Recuerde que las mejoras
más importantes en los lenguajes provienen de las pruebas que
hace el compilador: chequeo de tipos, gestión de excepciones,
etc., pero estas características no puede ir muy lejos. Debe
hacer el resto del camino creando un sistema robusto rellenando
las pruebas que verifican las características específicas de la
clase o programa concreto.
Escriba primero el código de las pruebas (antes de escribir la clase) para verificar que el diseño de la clase está completo. Si no puede escribir el código de pruebas, significa que no sabe que aspecto tiene la clases. En resumen, el echo de escribir las pruebas a menudo desvela características adicionales o restricciones que necesita la clase - esas características o restricciones no siempre aparecen durante el análisis y diseño.
Recuerde una regla fundamental de la ingeniería del software [87]: Todos los problemas del diseño de software se puede simplificar introduciendo una nivel más de indirección conceptual. Esta única idea es la pase de la abstracción, la principal cualidad de la programación orientada a objetos.
Haga clases tan atómicas como sea posible: Es decir, dé a cada clase un propósito único y claro. Si el diseño de su clase o de su sistema crece hasta ser demasiado complicado, divida las clases complejas en otras más simples. El indicador más obvio es tamaño total: si una clase es grande, FIXME: chances are it's doing demasiado y debería dividirse.
Vigile las definiciones de métodos largos. Una función demasiado larga y complicada es dificil y cara de mantener, y es problema que esté intentado hacer demasiado trabajo por ella misma. Si ve una función así, indica que, al menos, debería dividirse en múltiples funciones. También puede sugerir la creación de una nueva clase.
Vigile las listas de argumentos largas. Las llamadas a función se vuelven difíciles de escribir, leer y mantener. En su lugar, intente mover el método a una clase donde sea más apropiado, y/o pasele objetos como argumentos.
No se repita. Si un trozo de código se repite en muchos métodos de las clases derivadas, ponga el código en un método de la clase base e invóquelo desde las clases derivadas. No sólo ahorrará código, también facilita la propagación de los cambios. Puede usar una función inline si necesita eficiencia. A veces el descubrimiento de este código común añadirá funcionalidad valiosa a su interface.
Vigile las sentencias switch
o cadenas de
if-else
. Son indicadores típicos de código
dependiente del tipo, lo que significa que está
decidiendo qué código ejecutar basándose en alguna información
de tipo (el tipo exacto puede no ser obvio en
principio). Normalemente puede reemplazar este tipo de código
por herencia y polimorfismo; una llamada a una función
polimórfica efectuará la comprobación de tipo por usted, y hará
que el código sea más fiable y sencillo de extender.
Desde el punto de vista del diseño, busque y distinga cosas que cambian y cosas que no cambian. Es decir, busque elementos en un sistema que podrían cambiar sin forzar un rediseño, después encapsule esos elementos en clases. Puede aprender mucho más sobre este concepto en el capítulo Dessign Patterns del Volumen 2 de este libro, disponible en www.BruceEckel.com [88]
Tenga cuidado con las FIXME discrepancia. Dos objetos semánticamente diferentes puede tener acciones idénticas, o responsabilidades, y hay una tendencia natural a intentar hacer que una sea subclase de la otra sólo como beneficio de la herencia. Ese se llama discrepancia, pero no hay una justificación real para forzar una relación superclase/subclase donde no existe. Un solución mejor es crear una clase base general que produce una herencia para las dos como clases derivadas - eso require un poco más de espacio, pero sigue beneficiandose de la herencia y probablemente hará un importante descubrimiento sobre el diseño.
Tenga cuidado con la FIXME: limitación de la herencia. Los diseños más límpios añaden nuevas capacidades a las heredadas. Un diseño sospechoso elimina capacidades durante la herencia sin añadir otras nuevas. Pero las reglas están hechas para romperse, y si está trabajando con una librería antigua, puede ser más eficiente restringir una clase existente en sus subclases que restructurar la jerarquía de modo que la nueva clase encaje donde debería, sobre la clase antigua.
No extienda funcionalidad fundamental por medio de subclases. Si un elemento de la interfaz es esecial para una clase debería estár en la clase base, no añadido en una clase derivada. Si está añadiendo métodos por herencia, quizá debería repensar el diseño.
Menos es más. Empiece con una interfaz mínima a una clase, tan pequeña y simple como necesite para resolver el problema que está tratando, pero no intente anticipar todas las formas en las que se podría usar la clase. Cuando use la clase, descubrirá formas de usarla y deberá expandir la interface. Sin embargo, una vez que que la clase esté siendo usada, no podrá reducir la interfaz sin causar problemas al código cliente. Si necesita añadir más funciones, está bien; eso no molesta, únicamente obliga a recompilar. Pero incluso si los nuevos métodos reemplazan las funcionalidad de los antiguos, deje tranquila la interfaz existente (puede combinar la funcionalidad de la implementación subyacente si lo desea. Si necesita expandir la interfaz de un método existente añadiendo más argumentos, deje los argumentos existentes en el orden actual, y ponga valores por defecto a todos los argumentos nuevos; de este modo no perturbará ninguna de las llamadas antiguas a esa función.
Lea sus clases en voz alta para estar seguro que que suenan lógicas, refiriendose a las relación entre una clase base y una clase derivada com «es-un» y a los objetos miembro como «tiene-un».
Cuando tenga que decidir entre herencia y composición, pregunte si necesita hacer upcast al tipo base. Si la respuesta es no, elija composición (objetos miembro) en lugar de herencia. Esto puede eliminar la necesidad de herencia múltiple. Si hereda, los usuarios pensarán FIXME:they are supposed to upcast.
A veces, se necesita heredar para acceder a miembros protegidos de una clase base. Esto puede conducir a una necesidad de herencia múltiple. Si no necesita hacer upcast, primero derive una nueva clase para efectuar el acceso protegido. Entonces haga que la nueva clase sea un objeto miembro dentro de cualquier clase que necesite usarla, el lugar de heredar.
Típicamente, una clase base se usará principalmente para crear una interface a las clases que hereden de ella. De ese modo, cuando cree una clase base, haga que por defecto los métodos sean virtuales puros. El destructor puede ser también virtual puro (para forzar que los derivadas tengan que anularlo explicitamente), pero recuerde poner al destructor un cuerpo, porque todos destructores de la jerarquía se ejecutan siempre.
Cuando pone un método virtual puro en una clase, haga que todos
los métodos de la clase sean también viruales, y ponga un
constructor virtual. Esta propuesta evita sorpresas en el
comportamiento de la interfaz. Empiece a quitar la palabra
virtual
sólo cuando esté intentando optimizar y su
perfilador haya apuntado en esta dirección.
Use atributos para variaciones en los valores y métodos virtuales para variaciones en el comportamiento. Es decir, si encuentra una clase que usa atributos estáticos con métodos que cambian de comportamiento basandose en esos atributos, probablemente deberia rediseñarla para expresar las diferencias de comportamiento con subclases y métodos virtuales anulados.
If debe hacer algo no portable, cree una abstracción para el servicio y póngalo en una clase. Este nivel extra de indirección facilita la portabilidad mejor que si se distribuyera por todo el programa.
Evite la herencia múltiple. Estará a salvo de malas situaciones, especialmente cuando repare las interfaces de clases que están fuera de su control (vea el Volumen 2). Debería ser un programador experimentado antes de poder diseñar con herencia múltiple.
No use herencia privada. Aunque, está en el lenguaje y parece que tiene una funcionalidad ocasional, ello implica ambigüedades importantes cuando se combina con comprobación dinámica de tipo. Cree un objeto miembro privado en lugar de usar herencia privada.
Si dos clases están asociadas entre si de algún modo (como los contenedores y los iteradores). intente hacer que una de ellas sea una clase amiga anidada de la otro, tal como la Librería Estándar C++ hace con los interadores dentro de los contenedores (En la última parte del Capítulo 16 se muestran ejemplos de esto). No solo pone de manifiesto la asociación entre las clases, también permite que el nombre de la clase se pueda reutilizar anidándola en otra clase. La Librería Estándar C++ lo hace definiendo un clase iterador anidada dentro de cada clase contenedor, de ese modo los contenedores tienen una interface común. La otra razón por la que querrá anidar una clase es como parte de la implementación privada. En ese caso, el anidamiento es beneficioso para ocultar la implementación más por la asociación de clases y la prevención de la contaminación del espacio de nombres citada arriba.
La sobrecarga de operadores en sólo «azucar sintáctico:» una manera diferente de hacer una llamada a función. Is sobrecarga un operador no está haciendo que la interfaz de la clase sea más clara o fácil de usar, no lo haga. Cree sólo un operador de conversión automática de tipo. En general, seguir las directrices y estilo indicados en el Capítulo 12 cuando sobrecargue operadores.
No sea una víctima de la optimización prematura. Ese camino lleva a la locura. In particular, no se preocupe de escribir (o evitar) funciones inline, hacer algunas funciones no virtuales, afinar el código para hacerlo más eficiente cuando esté en las primer fase de contrucción del sistema. El objetivo principal debería ser probar el diseño, a menos que el propio diseño requiera cierta eficiencia.
Normalmente, no deje que el compilador cree los constructores,
destructores o el operator=
por usted. Los
diseñadores de clases siempre deberían decir qué debe hacer la
clase exactamente y mantenerla enteramente bajo su control. Si
no quiere costructor de copia u operator=
,
declarelos como privados. Recuerde que si crea algún
constructor, el compilador un sintetizará un constructor por
defecto.
Si su clase contiene punteros, debe crear el constructor de
copia, el operator=
y el destructor de la clase
para que funcione adecuadamente.
Cuando escriba un constructor de copia para una clase derivada, recuerde llamar explícitamente al constructor de copia de la clase base (también cuando se usan objetos miembro). (Vea el Capítulo 14.) Si no lo hace, el constructor por defecto será invocado desde la case base (o el objeto miembro) y con mucha probabilidad no hará lo que usted espera. Para invocar el constructor de copia de la clase base, pásele el objeto derivado desde el que está copiando:
Derived(const Derived& d) : Base(d) { // ...
Cuando escriba un operador de asignación para una clase derivada, recuerde llamar explícitamente al operador de asignación de la clase base. (Vea el Capítulo 14.) SI no lo hace, no ocurrirá nada (lo mismo es aplicable a los objetos miembro). Para invocar el operador de asignación de la clase base, use el nombre de la clase base y el operador de resolución de ámbito:
Derived& operator=(const Derived& d) { Base::operator=(d);
Si necesita minimizar las recompilaciones durante el desarrollo de un proyecto largo, use FIXME: demostrada en el Capítulo 5, y eliminela solo si la eficiencia en tiempo de ejecución es un problema.
Evite el preprocesador. Use siempre const
para
substitución de valores e inlines para las machos.
Mantenga los ámbitos tan pequeños como sea posible de modo que la visibilidad y el tiempo de vidad de los objetos sea lo más pequeño posible. Esto reduce el peligro de usar un objeto en el contexto equivocado y ello supone un bug dificil de encontrar. Por ejemplo, suponga que tiene un contenedor y un trozo de código que itera sobre él. Si copia el código para usarlo otro contenedor, puede que accidentalmente acabe usando el tamaño del primer contenedor como el límite superior del nuevo. Pero, si el primer contendor estuviese fuera del ámbito, podría detectar el error en tiempo de compilación.
Evite las variables globales. Esfuercese en pones los datos dentro de clases. En más probable que aparezcan funciones globales de forma natural que variables globales, aunque puede que después descubra que una función global puede encajar como método estático de una clase.
Si necesita declara una clase o función de una librería, hágalo
siempre incluyendo su fichero de cabecera. Por ejemplo, si
quiere crear una función para escribir en un
ostream
, no declare nunca el
ostream
por usted mismo, usando una
especificación de tipo incompleta como esta:
class ostream;
Este enfoque hace que su código sea vulnerabla a cambios en la
representación. (Por ejmplo, ostream
podrías ser en realidad un typedef
.) En lugar de lo
anterior, use siempre el ficheor de cabecera:
#include <iostream>
Cuando cree sus propias clases, si una librería es grande, proporciones a sus usuarios una versión abreviada del fichero de cabecera con especificaciones de tipo incompletas (es decir, declaraciones de los nombres de las clases) para los casos en que ellos puedan necesitar usar únicamente punteros. (eso puede acelerar las compilaciones.)
Cuando elija el tipo de retorno de una operador sobrecargado,
considere que ocurrirá if se encadenan expresiones. Retorne una
copia o referencia al valor (return *this
) de modo
que se pueda usar e una expresión encadenada (A = B =
C
). Cuando defina el operator=
, recuerde que
x=x
.
Cuando escriba una función, pase los argumentos por referencia constante como primera elección. Siempre que no necesite modificar el objeto que está pasando, esta es la mejor práctica porque es tan simple como si lo parasa por valor pero sin pagar el alto precio de construir y destruir un objeto local, que es lo que ocurre cuando se pasa por valor. Normalmente no se querrá preocupar demasiado de las cuestiones de eficiencia cuando esté diseñando y contruyendo su sistema, pero este hábito es una ganancia segura.
Tenga cuidado con los temporarios. Cuando esté optimizando, busque creaciones de temporarios, especialmente con sobrecarga de operadores. Si sus constructores y destructores son complicados, el coste de la creació y destrucción de temporarios puede ser muy alto. Cuando devuelva un valor en una función, intente siempre contruir el objeto «en el sitio» (in place) con una llamada al constructor en la sentencia de retorno:
return MyType(i, j);
mejor que
MyType x(i, j); return x;
La primera sentencia return
(también llamada
optimización de valor de retorno) evita una
llamada al constructor de copia y al destructor.
Cuando escriba constructores, considere las excepciones. En el mejor caso, el constructor no hará nada que eleve un excepción. En ese escenario, la clasé será compuesta y heredará solo de clases robustas, de modo que ellas se limpiarán automáticamente si se eleva una excepción. Si requiere punteros, usted es responsable de capturar sus propias excepciones y de liberar los recursos antes de elevar una excepción en su constructor. Si un contructor tiene que fallar, la acción apropiada es elevar una excepción.
En los constructores, haga lo mínimo necesario. No solo producirá una sobrecarga menor al crear objetos (muchas de las cuales pueden quedar fuera del control del programador), además la probabilidad de que eleven excepciones o causen problemas será menor.
La responsabilidad del destructor es la de liberar los recursos solicitados durante la vida del objeto, no sólo durante la construcción.
Utilice jerarquías de excepciones, preferiblemente derivadas de la jerarquía de excepción estándar de C++ y anidelas como clases públicas con la clase que eleva la excepción. La persona que captue las excepciónes puede capturar los tipos específicos de excepciones, seguida del tipo base. Si añade una nueva excepción derivada, el código de cliente anterior seguirá capturando la excepción por medio del tipo base.
Eleve las excepciones por valor y capturelas por referencia. Deje que el mecanismo de gestión de excepciones haga la gestión de memoria. Si eleva punteros como objetos en la excepción que han sido creados en el montículo, el que capture la excepción debe saber como liberar la excepción, lo cual implica un acoplamiento perjudicial. Si captura las excepciones por valor, causará que se creen temporarios; peor, las partes derivadas de sus objetos-excepción se pueden partir al hacer upcasting por valor.
No escriba sus propias clases plantilla a menos que debe. Mire primero en la Librería Estándar de C++, después en librerías de propósito específico. Adquiera habilidad en su uso y conseguirá incrementar mucho su productividad.
Cuando cree plantillas, escriba código que no dependa del tipo y ponga ese código en una clase base no-plantilla para evitar que el código aumente de tamaño sin necesidad. Por medio de herencia o composición, puede crear plantillas en las que el volumen de código que contienen es dependiente del tipo y por tanto esencial.
No use las funciones de <stdio>
, como
por ejemplo printf()
. Aprenda a usar
iostreams en su lugar; son FIXME:type-safe y type-extensible, y
mucho más potentes. El esfuerzo se verá recompensado con
regularidad. En general, use siempre librerías C++ antes que
librerías C.
Evite los tipos predefinidos de C. El soporte de C++ es por compatibilidad con C, pero son tipos mucho menos robustos que las clases C++, de modo que pueden complicar la depuración.
Siempre que use tipos predefinidos para variables globales o automáticas, no los defina hasta que pueda inicializarlos. Defina una variable por línea. Cuando defina punteros, ponga el '*' al lado del nombre del tipo. Puede hacerlo de forma segura si define una variable por línea. Este estilo suele resultar menos confuso para el lector.
Garantize que tiene lugar la inicialización en todos los
aspectos de su programa. Inicialice todos los atributos en la
lista de inicialización del constructor, incluso para los tipo
predefinidos (usando los pseudo-constructores). Usar la lista de
inicialización del constructor es normalmente más eficiente
cuando se inicializan subobjetos; si no se hace se invocará el
constructor por defecto, y acabará llamando a otros métodos
(probablemnte el operator=
) para conseguir la
inicialización que desea.
No use la forma MyType a = b;
para definir un
objeto. Esta es una de la mayores fuentes de confusión porque
llama a un contructor en lugar de al
operator=
. Por motivos de claridad, sea específico
y use mejor la forma MyType a(b);
. Los resultados
son idénticos, pero el lector no se podrá confundir.
Use los moldes explícitos descritos en el Capítulo 3. Un molde reemplaza el sistema normal de tipado y es un punto de error. Como los moldes explícitos separan los un-molde-lo hace-todo de C en clases de moldes bien-marcados, cualquiera que depure o mantenga el código podrá encontrar fácilmente todo los sitios en los que es más probable que sucedan errores lógicos.
Para que un programa sea robusto, cada componente debe ser robusto. Use todas las herramientas que proporciona C++: control de acceso, excepciones, constantes, comprobación de tipos, etc en cada clase que cree. De ese modo podrá pasar de una forma segura al siguiente nivel de abstracción cuando construya su sistema.
Use las constantes con corrección. Esto permite que el compilador advierta de errores que de otro modo serían sutiles y difíciles de encontrar. Esta práctica requiere de cierta disciplina y se debe usar de modo consistente en todas sus clases, pero merece la pena.
Use la comprobación de tipos del compilador en su
beneficio. Haga todas las compilaciones con todos los avisos
habilitados y arregle el código para eliminar todas las
advertencias. Escriba código que utilice los errores y
advertencias de compilación (por ejemplo, no use listas
de argumentos variables, que eliminar todas los comprobaciones
de tipos). Use assert()
para depurar, pero
use excepciones para los errores de ejecución.
Son preferibles los errores de compilación que los de ejecución. Intente manejar un error tan cerca del punto donde ocurre como sea posible. Es mejor tratar el error en ese punto que elevar una excepción. Capture cualqueir excepción en el manejador más cercano que tenga suficiente información para tratarla. Haga lo que pueda con la excepción en el nivel actual; si no puede resolver el problema, relance la excepción. (Vea el Volumen 2 si necesita más detalles.)
Si está usando las especificaciones de excepción (vea el Volumen
2 de este libro, disponible en www.BruceEckel.com, para
aprender sobre manejo de excepciones), instale su propia función
unexpected()
usando
set_unexpected()
. Su
unexpected()
debería registrar el error y
relanzar la excepción actual. De ese modo, si una función
existente es reemplazada y eleva excepciones, dispondrá de un
registro de FIXME:culprint y podrá modificar el código que la
invoca para manejar la excepción.
Cree un terminate()
definida por el usuario
(indicando un error del programador) para registrar el error que
causó la excepción, después libere los recursos del sistema, y
termine el programa.
Si un destructor llama a cualquier función, esas funciones
podrían elevar excepciones. Un destructor no puede elevar una
excepción (eso podría ocasionar una llamada a
terminate()
, lo que indica un error de
programación), así que cualquier destructor que llame a otras
funciones debe capturar y tratar sus propias excepciones.
No «decore» los nombres de sus atributos privados (poniendo guiones bajos, notación húngara, etc.), a menos que tenga un montón de valores globales ya existentes; en cualquier otro caso, deje que las clases y los espacios de nombres definan el ámbito de los nombres por usted.
Ponga atención a la sobrecarga. Una función no debería ejecutar código condicionalmente basandose en el valor de un argumento, sea por defecto o no. En su lugar, debería crear dos o más métodos sobrecargados.
Oculte sus punteros dentro de clases contenedor. Dejelos fuera
sólo cuando vaya a realizar operaciones con ellos. Los punteros
ha sido siempre la mayor fuente de errores. Cuando use
new
, intente colocar el puntero resultante en un
contenedor. Es preferible que un contenedor «posea»
sus punteros y sea responsable de la limpieza. Incluso mejor,
envuelva un puntero dentro de una clase; si aún así quiere que
parezca un puntero, sobrecargue operator->
y
operator*
. Si necesita tener un puntero
normal, inicialicelo siempre, preferiblemente con
la dirección de un objeto, o cero si es necesario. Asignele un
cero cuando le libere para evitar liberaciones múltiples.
No sobrecargue los new
y delete
globales. Hágalo siempre en cada clase. Sobrecargar las
versiones globales affecta la proyecto completo, algo que sólo
los creadores del proyecto debería controlar. Cuando sobrecargue
new
y delete
en las clases, no asume que
conoce el tamaño del objeto; alguien puede heredar de esa
clase. Use el argumento proporcionado. Si hace algo especial,
considere el efecto que podría tener en las clases derivadas.
Evite el troceado de objetos. Prácticamente nunca tiene sentido hacer upcast de un objeto por valor. Para evitar el upcast por valor, use métodos virtuales puros en su clase base.
A veces la agregación simple resuelve el problema. Un FIXME:«sistema conforme al pasajero» en una línea aérea consta en elementos desconectados: asiento, aire acondicionado, video, etc., y todavía necesita crear muchos más en un avión. ¿Debe crear miembros privados y construir una nueva interfaz completa? No - en este caso, los componentes también son parte de la interfaz pública, así que deberían ser objetos miembros públicos. Esos objetos tienen sus propias implementaciones privadas, que continúan seguras. Sea consciente de que la agregación simple no es una solución usan a menudo, pero que puede ocurrir.