Diseño de clase C ++ con invariante

7

He estado reflexionando sobre una pregunta realmente básica sobre hasta dónde llegar para hacer cumplir el invariante de una clase . Tal vez esté mal redactado, así que como ejemplo, digamos que quiero escribir una clase que almacene una paleta de colores limitada. El constructor de la clase debe tomar el tamaño de la paleta; la idea es que puede agregar tantos colores a la paleta como desee, pero restringe el tamaño de una política FIFO. Así que diremos que quiero restringir arbitrariamente este tamaño al rango de 1 a 32 ("nadie necesitará más de 32 colores").

Empecemos ...

class palette
{
public:
   palette(unsigned int max_size) : m_max_size{max_size} { }
private:
   unsigned int m_max_size;
};

Hasta ahora, todo bien. Excepto que no estoy haciendo nada para confirmar que el tamaño máximo está dentro de mi rango restringido, es decir, el invariante se puede romper desde la construcción.

(Solo para aclarar: esta clase no está diseñada para usarse en algunas bibliotecas públicas, es simplemente una clase interna para una sola aplicación)

Cuatro opciones vienen a la mente más allá de "no hacer nada":

  1. Ponga un comentario en la clase para pedirle a las personas que llaman que usen la clase correctamente.

    // Please only call with 1 <= max_size <= 32
    
  2. Afirma que está dentro del rango.

    assert(max_size >= 1 && max_size <= 32);
    
  3. Lanzar una excepción si está fuera del rango

    if (max_size < 1 || max_size > 32)
        throw std::invalid_argument();
    
  4. Haz que el compilador lo verifique convirtiéndolo en una plantilla

    template <unsigned int MAX>
    class palette
    {
    public:
        palette() { // no need for the argument any more
            static_assert(MAX >= 1 && MAX <= 32, "oops");
        }
    };
    

¿Las opciones # 1 se parecen un poco a una ilusión, pero tal vez sea lo suficientemente buena como para 'solo' una clase interna? Las opciones # 2 a # 4 son cada vez más exigentes en cuanto al tamaño, hasta el punto en que # 4 cambia la sintaxis para que el compilador informe un error si la clase no se usa correctamente.

Daría la bienvenida a cualquier pensamiento sobre esto. Entiendo que la respuesta probablemente comenzará con "Bueno, todo depende ...", pero solo estoy buscando algunas pautas generales o sugerencias alternativas.

    
pregunta WalderFrey 01.02.2016 - 12:01

2 respuestas

4

Iría con una afirmación. Sin más información, probablemente prefiera el no estático assert() .

Un comentario de hecho sería una ilusión. No solo se puede ignorar, sino que puede desactualizarse muy fácilmente si (¿cuándo?) Decide que 32 ya no debería ser el límite.

Si esta era una API pública con otros programadores usándola, la excepción sería beneficiosa, ya que tiende a proporcionar más información de depuración, especialmente si incluyó un mensaje de error significativo como "walderFrey :: El tamaño de la paleta debe estar entre 1 y 32 inclusive ". De lo contrario, es posible que tengan que profundizar en su código fuente y aprender cómo funciona su biblioteca solo para descubrir por qué falló esa afirmación aleatoria. Pero como esto es solo para su propio uso, no hay tanto beneficio en eso. Y también podría obtener el beneficio de rendimiento (aunque sea pequeño) de que el assert() desaparezca en compilaciones que no sean de depuración.

También escuché que argumentaba que las excepciones deberían indicar condiciones imprevisibles y excepcionales que probablemente no se repitan o que ni siquiera se pueden arreglar, mientras que las afirmaciones deben indicar errores de programación que definitivamente pueden y deben ser fijo. Generalmente estoy de acuerdo con esta guía cuando no hay ninguna API pública de la que preocuparse.

La solución de la plantilla es clara, pero eso plantea una pregunta mayor: ¿una paleta de tamaño 1 y una paleta de tamaño 32 deben considerarse del mismo tipo? ¿Tiene sentido asignar una a la otra? Si no tiene sentido, eso es un beneficio adicional de implementar esta clase como plantilla. Pero no debe decidir si hacer de esta clase una plantilla únicamente porque un static_assert() es ligeramente más agradable que un assert() . Un assert() regular es definitivamente "suficientemente bueno". Normalmente, el código genérico que involucra plantillas es más complejo, por lo que prefiero una clase "normal" hasta que sienta que la complejidad está justificada, por lo tanto, recomendaría assert() a menos que sienta que ir con las plantillas está justificado por otras razones. / p>     

respondido por el Ixrec 01.02.2016 - 12:33
1

Utilice la garantía más sólida que desea mantener en el futuro inmediato.

El Método 4 (template / static assert) asegura que el invariante siempre es verdadero en un programa compilado. Sin embargo, esto implica que la entrada debe conocerse en el momento de la compilación, lo que no siempre es posible.

El método 2 (aserción) y 3 (excepción) pueden fallar en el tiempo de ejecución, por lo que ofrecen una garantía más débil que el método 4. La elección entre una aserción y una excepción depende de la audiencia (quién usará la clase), estándar de codificación aplicable y las posibles repercusiones de no mantener el invariante. Para un tratamiento en profundidad, consulte John Lakos - Programación defensiva bien hecho .

Utilice una afirmación cuando considere que se trata de un error lógico (o de programador), no es probable que el hecho de romper el invariante cause daños (por ejemplo, dañar el hardware, dañar datos) y no se puede hacer nada para evitar el error sin cambiar / recompilando el código. Las aserciones normalmente solo se verifican en una compilación de depuración, no en compilaciones de lanzamiento.

Lanzar una excepción si el error puede ser causado por "circunstancias ambientales" (por ejemplo, falla de la red) o, en algunos casos, una entrada no válida o si continúa a pesar del error puede causar daños. Por supuesto, la excepción debe manejarse en algún lugar del código de llamada, lo que esencialmente significa que al lanzar una excepción, la semántica de su clase se vuelve más compleja.

El Método 1 (comentario) es la documentación del código y esto debe hacerse siempre además de uno de los otros métodos. No desea forzar a alguien que use esta clase a tener que mirar el código fuente para averiguar cómo usarlo. Eso debe quedar claro en la documentación. Si maneja el error lanzando una excepción, también debe documentarlo.

    
respondido por el D Drmmr 11.02.2016 - 11:59

Lea otras preguntas en las etiquetas