2012-07-13 39 views
7

Después de muchos años de codificación de software científico en C++, todavía no puedo acostumbrarme a las excepciones y no tengo ni idea de cuándo debo usarlas. Sé que usarlos para controlar el flujo de programas es un gran no-no, pero aparte de eso ... considere el siguiente ejemplo (extracto de una clase que representa una máscara de imagen y permite al usuario agregar áreas como polígonos):No estoy seguro de cuándo usar excepciones en C++

class ImageMask 
{ 
public: 
    ImageMask() {} 
    ImageMask(const Size2DI &imgSize); 

    void addPolygon(const PolygonI &polygon); 

protected: 
    Size2DI imgSize_; 
    std::vector<PolygonI> polygons_; 
}; 

El constructor predeterminado para esta clase crea una instancia inútil, con un tamaño de imagen indefinido. No quiero que el usuario pueda agregar polígonos a dicho objeto. Pero no estoy seguro de cómo manejar esa situación. Cuando el tamaño no está definido, y addPolygon() se llama, en caso de que:

  1. retorno En silencio,
  2. assert (imgSize_.valid) para detectar violaciónes de código usando esta clase y solucionarlos antes de un lanzamiento,
  3. , lanzar una excepcion?

La mayoría de las veces voy con 1) o 2) (dependiendo de mi estado de ánimo), porque me parece que las excepciones son costosas, desordenadas y simplemente excesivas para un escenario tan simple. ¿Alguna idea por favor?

+4

¿Evitar la construcción predeterminada? Evita la creación de instancias 'ImageMask' inválidas. – hmjd

+1

¿Por qué necesita un constructor predeterminado? –

+0

Un constructor predeterminado puede ser útil para que pueda colocar objetos de ese tipo en ciertos contenedores. – MadScientist

Respuesta

6

La regla general es que lanza una excepción cuando no puede realizar la operación deseada. Entonces, en su caso, sí, tiene sentido lanzar una excepción cuando se llama addPolygon y el tamaño no está definido o es inconsistente.

Volver silenciosamente es casi siempre lo incorrecto. assert no es una buena técnica de manejo de errores (es más una técnica de diseño/documentación).

Sin embargo, en su caso, un rediseño de la interfaz para imposibilitar o improbable una condición de error puede ser mejor. Por ejemplo, algo como esto:

class ImageMask 
{ 
public: 
    // Constructor requires collection of polygons and size. 
    // Neither can be changed after construction. 
    ImageMask(std::vector<PolygonI>& polygons, size_t size); 
} 

o como esto

class ImageMask 
{ 
public: 
    class Builder 
    { 
    public: 
     Builder(); 
     void addPolygon(); 
    }; 

    ImageMask(const Builder& builder); 
} 

// used like this 
ImageMask::Builder builder; 
builder.addPolygon(polyA); 
builder.addPolygon(polyB); 
ImageMask mask(builder); 
1

Para mí, es un 1 ninguna opción. Si es 2 o 3 depende del diseño de su programa/biblioteca, si considera (y documenta) la máscara de imagen de construcción predeterminada y luego agrega polígonos con un uso válido o no válido de su componente. Esta es una decisión de diseño importante. Recomiendo leer this article por Matthew Wilson.

en cuenta que tiene más opciones:

  • Invent su propio afirman que siempre llama std :: terminará y no hace registro adicional
  • Desactivar el constructor por defecto (como otros ya se ha señalado) - esto es mi favorito
2

Intentaré evitar cualquier situación donde sea posible crear datos que estén en algún tipo de estado inútil. Si necesita un polígono que no esté vacío, no permita que se creen polígonos vacíos y se ahorrará muchos problemas porque el compilador exigirá que no haya polígonos vacíos.

Nunca uso devoluciones silenciosas, porque ocultan errores y esto hace que encontrar errores sea mucho más complicado de lo que debe ser.

Uso las afirmaciones cuando detecto que el programa está en un estado en el que solo puede estar, si hay un error en el software. En su ejemplo, si comprueba en el administrador que toma un Tamaño2DI, que este tamaño no está vacío, que afirmar si el tamaño almacenado no está vacío, es útil para detectar errores. Las afirmaciones no deberían tener un efecto secundario y debe ser posible eliminarlas, sin cambiar el comportamiento del software. Los encuentro muy útiles para encontrar mis propios errores y documentar el estado actual del objeto/función, etc.

Si es muy probable que un llamador de una función maneje un error de tiempo de ejecución, usaría valores de retorno convencionales. Si es muy probable que esta situación de error deba comunicarse a través de varias llamadas de función en la pila de llamadas, prefiero las excepciones. En duda, ofrezco dos funciones.

Saludos cordiales Torsten

1
  1. "En silencio volver" - que es real 'el gran no-no'. El programa debe saber lo que está mal.
  2. "afirmar": la segunda regla es que afirma usar solo si no se pudo restaurar el flujo del programa normal.
  3. "excepción de lanzamiento" - sí, esta buena y correcta técnica. Solo cuídate de la seguridad de las excepciones. Hay muchos artículos sobre la codificación de excepción segura en GotW.

No te preocupes excepciones. Ellos no muerden. :) Si tomas esta técnica lo suficiente, serás un codificador fuerte. ;)

+0

Es curioso que las excepciones sean tan fáciles y normales de usar en Java, mientras que con C++ son tan incomprendidas – marcinj

+0

Nada que sorprender. Java maneja tu memoria por sí mismo; Cpp-developer puede (y afortunadamente o desafortunadamente) debe administrar su memoria por su cerebro. ;) –

Cuestiones relacionadas