2010-09-07 20 views
12

tengo una jerarquía de clases y cada clase tiene en ella una clase de excepción, derivó en una jerarquía paralela, por lo tanto ...enumeración de las clases derivadas

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class BaseException : public std::exception 
{ 
    enum {THIS_REASON, THAT_REASON}; 
}; 

class DerivedException : public BaseException 
{ 
    // er...what? 
}; 

me gustaría, en la clase DerivedException, para extender el tipo de enumeración para incluir un nuevo valor THE_OTHER_REASON, para que la clase DerivedException pueda contener cualquiera de los tres valores.

Antes que nada, ¿debería querer hacer esto? ¿Parece una práctica razonable? Si es así, ¿cómo lo hago? Si no, ¿qué alternativas recomendarías?

EDITAR: Se ha sugerido un posible duplicado here, pero las soluciones sugeridas son diferentes porque esa pregunta es para C# y esto para C++.

+0

posible duplicado de [Enum "herencia"] (http: // stackoverflow.com/questions/757684/enum-inheritance) –

Respuesta

10

Desde el punto de vista OO, no es razonable. Como dice que DerivedExceptiones-aBaseException, sus posibles motivos deben ser un subconjunto del BaseException, no un superconjunto. De lo contrario, finalmente rompes el Liskov Substitution Principle.

Además, dado que las enumeraciones de C++ no son clases, no puede extenderlas o heredarlas. Se puede definir razones adicionales en una enumeración separada dentro DerivedException, pero en última instancia, a continuación, te encuentras con el mismo problema descrito anteriormente:

class DerivedException : public BaseException 
{ 
    enum { 
    SOME_OTHER_REASON = THAT_REASON + 256, // allow extensions in the base enum 
    AND_ANOTHER_REASON 
    }; 
    ... 
}; 

... 
try { 
    ... 
} catch (BaseException& ex) { 
    if (ex.getReason() == BaseException::THIS_REASON) 
    ... 
    else if (ex.getReason() == BaseException::THAT_REASON) 
    ... 
    else if (ex.getReason() == ??? what to test for here ???) 
    ... 
} 

Lo que puede hacer en su lugar es definir una subclase excepción por separado para cada una razón distinta. Entonces puede manejarlos polimórficamente (si es necesario). Este es el enfoque de la biblioteca estándar de C++, así como de otras bibliotecas de clases. Por lo tanto, se adhiere a las convenciones, lo que hace que su código sea más fácil de entender.

+0

+1 para el Principio de sustitución Liskov. Si se extiende, entonces necesita algún tipo de mapeo de regreso al conjunto base de códigos. –

+0

pero esto tiene la misma restricción que la mía. No puede estar seguro si el valor no es de otra enumeración cuando usa más de 2 enum derivadas – kravemir

+0

@Miro, aparentemente no se dio cuenta de que no recomiendo extender enumeraciones de ninguna manera :-) –

2

Puede hacer esto, ya que utiliza enumeraciones sin nombre. Supongo que usas el tipo entero para almacenar el motivo de la excepción.

class DerivedException : public BaseException 
{ 
    enum { YET_ANOTHER_REASON = THAT_REASON + 1, EVEN_MORE_REASON}; 
}; 

No codifico razones como códigos. Preferiría clases de excepciones especializadas, por lo que solo puedo capturar los tipos de excepción que puedo manejar en un momento.

1

Primero: no se puede derivar enum; simplemente no tienes suerte allí.

En segundo lugar, parece que estás pensando demasiado en tu modelo de excepción. En lugar de hacer que cada clase use una excepción derivada personalizada específica de esa clase, cree una serie de excepciones que tengan valor semántico que sus clases podrían arrojar. Por ejemplo, si está lanzando porque un argumento requerido era nulo, puede arrojar un NullArgumentException. Si está lanzando debido a un desbordamiento matemático, puede arrojar un ArithmeticOverflowException.

En su modelo enum, está poniendo el valor semántico en la enumeración; Sugiero que coloque el valor semántico en el tipo de la excepción. Puede atrapar varios tipos de excepciones, recuerde.

Para ver ejemplos de excepciones con valores semánticos, consulte la biblioteca estándar de C++ o, para una lista más extensa, las bibliotecas de Java o C#.

1

No, no es razonable como está ahora. Para que el tipo derivado tenga algún significado (desde el punto de vista del principio de sustitución de Liskov), debe haber un comportamiento polimórfico en la clase base.

Se podría añadir un virtual int GetError() const a la clase base y dejar que las clases derivadas anularlo, pero entonces un usuario de un BaseException* o BaseException& no tendría ninguna pista en cuanto a lo que el código de error devuelto por las clases derivadas significaba.

Yo separaría los valores del código de error de las clases.

9

Para mí, parece más razonable tener una clase de excepción por cada motivo de excepción. Al manejar una excepción, generalmente no es interesante saber qué clase arrojó la excepción, pero por qué razón se lanzó la excepción.

Si desea mantener su diseño: C++ no permite que le permite ampliar una enumeración existente, pero puede crear una nueva enumeración que comienza en una anterior fue apagado:

class BaseException : public std::exception 
{ 
    enum {THIS_REASON, THAT_REASON, END_OF_BASE_REASONS }; 
}; 

class DerivedException : public BaseException 
{ 
    enum {OTHER_REASON = BaseException::END_OF_BASE_REASONS }; 
}; 
1

lo haría como, en la clase DerivedException, extender el tipo de enumeración para incluir un nuevo valor THE_OTHER_REASON, de modo que la clase DerivedException pueda contener cualquiera de los tres valores.

Simplemente asigne el primer valor de una nueva enumeración. Esto funciona ya que solo está usando la enumeración como una forma de declarar constantes.

class DerivedException : public BaseException 
{ 
    enum {THE_OTHER_REASON = THAT_REASON + 1, THE_REALLY_OTHER_REASON, ETC}; 
}; 

El problema es que realmente no se puede esperar que la singularidad de las enumeraciones entre las clases derivadas, sin definir una "cadena" de las clases derivadas (es decir, uno comienza sus enumeraciones después de otro). Sin embargo, si solo necesita exclusividad dentro de una sola rama del árbol de clases de excepción, puede funcionar bien.

0

La "razón" de la excepción es muy probablemente algo que debe mostrarse al usuario o registrarse, por lo que una cadena parece ser más apropiada, por lo que puede usarse en qué().
Si es más que eso, se necesitan más subclases.

1

No hay ninguna manera nativa para hacerlo, pero se puede hacer fácilmente con define: Aquí es pequeño ejemplo:

#define _Enum1_values a, b, c, d 

enum Enum1 { 
    _Enum1_values 
}; 

// there aren't strongly typed enums 
class A { 
public: 
    enum Enum2 { 
     _Enum1_values, e, f 
    }; 
}; 

// there aren't strongly typed enums 
class B { 
public: 
    enum Enum3 { 
     _Enum1_values, g, h 
    }; 
}; 


#include <iostream> 

int main() { 
    std::cout << "Enum1::d: " << d << '\n'; 
    std::cout << "Enum2::d: " << A::d << '\n'; 
    std::cout << "Enum2::e: " << A::e << '\n'; 
    std::cout << "WARNING!!!: Enum2::e == Enum3::g: " << (A::e == B::g) << '\n'; 
} 
+1

Aparte de los valores idénticos entre las enumeraciones "derivadas", el problema más grave es que cualquier extensión de 'Enum1' rompe las enumeraciones 'derivadas'. Actualizarlos no se puede hacer de manera automática, y depender exclusivamente de la disciplina y la memoria de los desarrolladores es propenso a fallar a largo plazo. –

Cuestiones relacionadas