Pregunta

En primer lugar, me disculpo por el largo período previo a una pregunta tan simplista.

Estoy implementando una clase que sirve como un índice 1 dimensional muy largo en una curva de relleno de espacio o la n-tupla que representa la coordenada cartesiana a la que corresponde el índice.

class curvePoint
{
public:
    friend class curveCalculate;

    //Construction and Destruction
    curvePoint(): point(NULL), dimensions(0) {}
    virtual ~curvePoint(){if(point!=NULL) delete[] point;}

    //Mutators
    void convertToIndex(){ if(isTuple()) calc(this); }
    void convertToTuple(){ if(isIndex()) calc(this); }
    void setTuple(quint16 *tuple, int size);
    void setIndex(quint16 *index, int size);
    void setAlgorithm(curveType alg){algorithm = alg;}

    //Inspectors
    bool isIndex(){return current==Index;}
    bool isTuple(){return current==Tuple;}
    size_t size(){return dimensions;}
    quint16 operator[](size_t index);

    enum curveType{HilbertCurve, ZCurve, GrayCodeCurve};
    enum status{Index, Tuple};

private:
    curveCalculate calc;
    curveType algorithm;
    quint16 *point;
    size_t dimensions;
    status current;
};

(La longitud de la matriz señalada por punto es dimensiones )

De todos modos en la implementación del operador [], me preguntaba cuál es el mejor método para lograr la verificación de límites. Quiero evitar lanzar excepciones si es posible, y el rango completo de valores es utilizable para cada número en la matriz, por lo que tampoco es posible un valor especial para devolver en caso de un error fuera de los límites;

Estaba pensando en algo como esto, aunque implementado en la definición de clase:

quint16 curvePoint::operator[](size_t index)
{
    return point[ index % dimensions ];
}

Esto hace que nunca salgamos de los límites de la matriz y, si está bien documentado, creo que estaría bien; Sin embargo, soy cauteloso con esta implementación en particular.

¿Esto parece aceptable para otros? ¿Hay alguna otra forma de verificar los límites sin dejar de satisfacer mis limitaciones?

Editar: El cálculo de cosas como las curvas de Hilbert, etc. es muy desordenado, lo suficientemente desordenado que no quiero la interfaz adicional para las bibliotecas stl en el camino.

Además, debido a que tendré que convertir muchos miles de estos cada vez que se consulte la base de datos multidimensional, no quiero el costo adicional de las llamadas a la función stl en la mezcla, si es posible.

Prefiero la idea de la afirmación; pero, si recuerdo correctamente que se rompe en las versiones de lanzamiento, ¿no es así?

Supongo que puedo usar excepciones, eso parece ser lo que todos están apoyando, pero estoy usando las bibliotecas Qt y esas evitan excepciones tanto para el rendimiento como para la portabilidad y esperaba hacer lo mismo.

¿Fue útil?

Solución

La solución más fácil es hacer lo mismo que C ++. Esto limita la cantidad de sorpresas que experimentarán sus usuarios.

C ++ en sí mismo es bastante consistente. Tanto el [] incorporado en los punteros como el std::vector::operator[] tienen un comportamiento indefinido si usa un índice de matriz fuera de límite. Si desea verificar los límites, sea explícito y use std::vector::at

Por lo tanto, si hace lo mismo para su clase, puede documentar el comportamiento fuera de límite como " standard " ;.

Otros consejos

  

De todos modos en la implementación de   operador [] Me preguntaba cuál es el   mejor método para lograr la verificación de límites   es. Quiero evitar tirar   excepciones si es posible, y el   la gama completa de valores es utilizable para   cada número en la matriz por lo que un especial   valor a devolver en caso de un fuera de   el error de límites tampoco es posible;

Entonces las opciones restantes son:

  • Diseño flexible. Lo que hiciste. " Fix " la entrada no válida para que intente hacer algo que tenga sentido. Ventaja: la función no se bloqueará. Desventaja: Como resultado, las personas que llaman Clueless que acceden a un elemento fuera de límites recibirán una mentira Imagine un edificio de 10 pisos con pisos del 1 al 10:
  

Usted: " ¿Quién vive en el 3er piso? "

     

Yo: "Mary".

     

Usted: " ¿Quién vive en el noveno piso? "

     

Yo: "Joe".

     

Usted: " ¿Quién vive en el piso 1,203? "

     

Yo: (Espera ... 1,203% 10 = 3 ...)    > " Mary " .

     

Usted: " Wow, Mary debe disfrutar de excelentes vistas desde allá arriba . ¿Entonces es propietaria de dos apartamentos? & Quot;

  • Un parámetro de salida bool indica éxito o fracaso. Esta opción generalmente termina en un código poco usable. Muchos usuarios ignorarán el código de retorno. Todavía le queda lo que devuelve en el otro valor de retorno.

  • Diseño por contrato. Afirme que la persona que llama está dentro de los límites. (Para un enfoque práctico en C ++, consulte ¿Una excepción o un error? Por Miro Samek o Soporte simple para diseño por contrato en C ++ por Pedro Guerreiro .)

  • Devuelve un System.Nullable<quint16> . Vaya, espera, esto no es C #. Bueno, podría devolver un puntero a una quint16. Por supuesto, esto tiene muchas implicaciones que no discutiré aquí y que probablemente hagan que esta opción no sea utilizable.

Mis opciones favoritas son:

  • Para la interfaz pública de una biblioteca publicada públicamente: se comprobará la entrada y se lanzará una excepción. Descartó esta opción, por lo que no es una opción para usted. Todavía es mi opción para la interfaz de una biblioteca publicada públicamente.
  • Para código interno: Diseño por contrato.

Para mí, esta solución es inaceptable porque puede estar ocultando un error muy difícil de encontrar. Lanzar una excepción fuera de rango es el camino a seguir, o al menos poner una afirmación en la función.

Si lo que necesita es algún tipo de " circular " matriz de puntos, entonces su solución está bien. Sin embargo, para mí, parece como ocultar el mal uso del operador de indexación detrás de & Quot; safe & Quot; lógica, por lo que estaría en contra de su solución propuesta.

Si no desea permitir el desbordamiento del índice, puede verificar y lanzar una excepción.

quint16 curvePoint::operator[](size_t index)
{
    if( index >= dimensions)
    {
       throw std::overflow_error();
    }
    return point[ index ];
}

Si desea tener menos sobrecarga, puede evitar una excepción, mediante el uso de aserciones de tiempo de depuración (suponga que el índice proporcionado siempre es válido):

quint16 curvePoint::operator[](size_t index)
{
    assert( index < dimensions);
    return point[ index ];
}

Sin embargo, sugiero que, en lugar de usar miembros de punto y dimensión, use std :: vector < quint16 > para almacenamiento de puntos. Ya tiene un acceso basado en índices que puede usar:

quint16 curvePoint::operator[](size_t index)
{
    // points is declared as std::vector< quint16> points;
    return points[ index ];
}

Tener un operador [] que nunca falla suena bien, pero puede ocultar errores más adelante, si una función de llamada utiliza un desplazamiento ilegal, encuentra un valor desde el principio del búfer y continúa como si ese fuera un valor válido.

El mejor método para lograr la verificación de límites sería agregar una afirmación.

quint16 curvePoint::operator[](size_t index)
{
    assert(index < dimensions);
    return point[index];
}

Si su código ya depende de las bibliotecas de Boost, es posible que desee utilizar BOOST_ASSERT en su lugar.

Si fuera usted, seguiría el ejemplo establecido por stl.

En este caso, std::vector proporciona dos métodos: at que tiene los límites marcados y operator[] que no lo está. Esto permite al cliente decidir con qué versión usar. Definitivamente no usaría el % size(), ya que esto solo oculta el error. Sin embargo, la verificación de límites agregará una gran cantidad de gastos generales al iterar sobre una colección grande, por eso debería ser opcional. Aunque estoy de acuerdo con otros pósters en que la afirmación es una muy buena idea, ya que esto solo causará un impacto en el rendimiento en las versiones de depuración.

También debe considerar devolver referencias y suministrar versiones const y no const. Aquí están las declaraciones de funciones para <=> :

reference at(size_type _Pos);
const_reference at(size_type _Pos) const;

reference operator[](size_type _Pos);
const_reference operator[](size_type _Pos) const;

Como buena regla general, si no estoy seguro de cómo especificar una API, busco ejemplos de cómo otros especifican API similares. Además, cuando uso una API, intento juzgarla o calificarla, encontrar los bits que me gustan y no me gustan.

Gracias al comentario sobre la función C # en la publicación de Daniel Daranas, he logrado encontrar una posible solución. Como dije en mi pregunta, estoy usando las bibliotecas Qt. Ahí puedo usar QVariant. QVariant se puede establecer en un estado no válido que la función que lo recibe puede verificar. Entonces el código se convertiría en algo así como:

QVariant curvePoint::operator[](size_t index){
    QVariant temp;
    if(index > dimensions){
        temp = QVariant(QVariant::Invalid);
    }
    else{
        temp = QVariant(point[index]);
    }

    return temp;
}

Por supuesto, esto tiene el potencial de insertar un poco de sobrecarga en la función, por lo que otra posibilidad es usar una plantilla de par.

std::pair<quint16, bool> curvePoint::operator[](size_t index){
    std::pair<quint16, bool> temp;
    if(index > dimensions){
        temp.second = false;
    }
    else{
        temp.second = true;
        temp.first = point[index];
    }
    return temp;
}

O podría usar un QPair, que tiene exactamente la misma funcionalidad y lo haría para que el STL no necesite estar vinculado.

Quizás podría agregar un " fuera de los límites " excepción al operador [] (o al menos una afirmación).

Esto debería detectar cualquier problema, especialmente al depurar.

A menos que esté malentendiendo drásticamente algo,

return point[ index % dimensions ];

no es la comprobación de límites en absoluto. Está devolviendo un valor real desde una parte totalmente diferente de la línea, lo que hará que sea mucho más difícil detectar errores.

Yo tampoco:

  1. Lanza una excepción o una afirmación (aunque dijiste que no quieres hacerlo)
  2. Simplemente desreferenciar el punto más allá de la matriz en un " natural " manera (es decir, simplemente omita cualquier comprobación interna). La ventaja sobre el% es que es más probable (aunque indefinido es indefinido) obtener & Quot; raro & Quot; valores y / o una infracción de acceso

Al final, la persona que llama está violando sus condiciones previas, y puede hacer lo que quiera. Pero creo que estas son las opciones más razonables.

Considere también lo que C & # 259; t & # 259; lin dijo sobre la incorporación de colecciones STL incorporadas si eso es razonable.

Su solución sería buena si proporcionara acceso a los puntos de una forma elíptica. Pero conducirá a errores muy desagradables si lo usa para funciones geométricas arbitrarias, porque a sabiendas proporciona un valor falso.

El operador de módulo funciona sorprendentemente bien para los índices de matriz, también implementa índices negativos (es decir, point[-3] = point[dimensions - 3]). Es fácil trabajar con él, por lo que personalmente recomendaría el operador de módulo siempre que esté bien documentado.

Otra opción es dejar que la persona que llama elija la política fuera de los límites. Considere:

template <class OutOfBoundsPolicy>
quint16 curvePoint::operator[](size_t index)
{
    index = OutOfBoundsPolicy(index, dimensions);
    return point[index];
}

Entonces podría definir varias políticas que la persona que llama puede elegir. Por ejemplo:

struct NoBoundsCheck {
    size_t operator()(size_t index, size_t /* max */) {
        return index;
    }
};

struct WrapAroundIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        return index % max;
    }
};

struct AssertIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        assert(index < max);
        return index % max;
    }
};

struct ThrowIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) throw std::domain_error;
        return index;
    }
};

struct ClampIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) index = max - 1;
        return index;
    }
};
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top