Lors de la mise en œuvre de l'opérateur [], comment dois-je inclure la vérification des limites?

StackOverflow https://stackoverflow.com/questions/1026042

  •  06-07-2019
  •  | 
  •  

Question

Tout d'abord, je m'excuse pour la longue période qui a précédé une question aussi simpliste.

J'implémente une classe qui sert d'indice 1 très long sur une courbe de remplissage d'espace ou le n-tuple représentant la coordonnée cartésienne à laquelle correspond cet index.

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 longueur du tableau pointé par le point est dimensions )

.

Quoi qu'il en soit, dans la mise en œuvre de l'opérateur [], je me demandais quelle était la meilleure méthode pour effectuer la vérification des limites. Je souhaite éviter autant que possible de lancer des exceptions, et que toute la plage de valeurs est utilisable pour chaque nombre dans le tableau, de sorte qu'aucune valeur spéciale à renvoyer en cas d'erreur hors limites ne soit pas possible non plus;

Je pensais à quelque chose comme ça bien que cela soit implémenté dans la définition de classe:

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

Cela fait en sorte que nous ne quittions jamais les limites du tableau et si cela est bien documenté, je pense que tout irait bien; Néanmoins, je suis au courant de cette mise en œuvre particulière.

Cela semble-t-il acceptable pour les autres? Existe-t-il un autre moyen de vérifier les limites tout en satisfaisant mes contraintes?

Modifier: Le calcul d'éléments tels que les courbes de Hilbert, etc., est extrêmement complexe, de sorte que je ne souhaite pas utiliser l'interface supplémentaire pour les bibliothèques stl.

De plus, étant donné que je devrai en convertir plusieurs milliers à chaque interrogation de la base de données multidimensionnelle, je ne veux pas du tout le coût supplémentaire des appels de fonction stl dans le mix, si possible.

J'aime assez l'idée de l'affirmation; mais, si je me souviens bien, les ruptures de versions ne le font pas?

Je suppose que je peux utiliser des exceptions, cela semble être ce que tout le monde recherche, mais j'utilise les bibliothèques Qt et celles-ci évitent les exceptions pour les performances et la portabilité et j'espérais en faire autant.

Était-ce utile?

La solution

La solution la plus simple est de faire comme C ++ lui-même. Cela limite le nombre de surprises que vos utilisateurs vont vivre.

C ++ lui-même est relativement cohérent. Les pointeurs [] intégrés et std::vector::operator[] ont tous deux un comportement indéfini si vous utilisez un index de tableau non lié. Si vous voulez vérifier les limites, soyez explicite et utilisez std::vector::at

Par conséquent, si vous faites la même chose pour votre classe, vous pouvez documenter le comportement hors limites en tant que & "standard &";.

.

Autres conseils

  

Quoi qu'il en soit dans la mise en œuvre de   opérateur [] Je me demandais ce que le   meilleure méthode pour réaliser la vérification des limites   est. Je veux éviter de jeter   des exceptions si possible, et le   gamme complète de valeurs est utilisable pour   chaque numéro dans le tableau si une spéciale   valeur à retourner en cas de sur   erreur des bornes n'est pas possible non plus;

Les options restantes sont les suivantes:

  • Conception flexible. Ce que vous avez fait. " Fixer " l'entrée non valide pour qu'elle essaie de faire quelque chose de logique. Avantage: la fonction ne plante pas. Inconvénient: les appelants désemparés qui accèdent à un élément hors limites obtiendront un mensonge . Imaginez un bâtiment de 10 étages avec les étages 1 à 10:
  

Vous: & "Qui habite au 3ème étage? &";

     

Moi: & "Marie &";

     

Vous: & "Qui habite au 9ème étage? &";

     

Moi: & "Joe &";

     

Vous: & "Qui habite au 1 203ème étage? &";

     

Moi: (attendez ... 1 203% 10 = 3 ...)    > & "Marie &"; .

     

Vous: & "Wow, Mary doit profiter de superbes vues de là-haut . Alors, elle possède deux appartements alors? & ";

  • Un paramètre de sortie bool indique le succès ou l'échec. Cette option aboutit généralement à un code peu utilisable. De nombreux utilisateurs vont ignorer le code de retour. Il vous reste toujours ce que vous retournez dans l'autre valeur de retour.

  • Conception par contrat. Indiquez que l'appelant est à l'intérieur des limites. (Pour une approche pratique en C ++, voir Une exception ou un bug? De Miro Samek ou Prise en charge simple de la conception par contrat en C ++ par Pedro Guerreiro .)

  • Renvoyez un System.Nullable<quint16> . Oups, attendez, ce n'est pas C #. Eh bien, vous pouvez renvoyer un pointeur sur un quint16. Cela a bien entendu de nombreuses implications dont je ne parlerai pas ici et qui rendent probablement cette option inutilisable.

Mes choix préférés sont les suivants:

  • Pour l'interface publique d'une bibliothèque publiée: L'entrée sera vérifiée et une exception sera levée. Vous avez exclu cette option, donc ce n'est pas une option pour vous. C’est toujours mon choix pour l’interface d’une bibliothèque publiée publiquement.
  • Pour le code interne: Conception par contrat.

Pour moi, cette solution est inacceptable car vous pouvez cacher un bogue très difficile à trouver. Lancer une exception hors limites est le chemin à parcourir, ou du moins mettre une assertion dans la fonction.

Si vous avez besoin d’une sorte de " circulaire " tableau de points, alors votre solution est ok. Cependant, pour moi, cela ressemble à cacher les erreurs de l'opérateur d'indexation derrière certains & "Safe &"; logique, donc je serais contre votre solution proposée.

Si vous ne voulez pas autoriser le débordement d'index, vous pouvez vérifier et lever une exception.

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

Si vous souhaitez avoir moins de temps système, vous pouvez éviter les exceptions en utilisant des assertions de temps de débogage (supposons que l'index fourni est toujours valide):

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

Cependant, je suggère que, au lieu d'utiliser des membres de point et de dimension, utilisez un std :: vector < quint16 > pour le stockage de points. Il dispose déjà d'un accès basé sur un index que vous pouvez utiliser:

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

Avoir un opérateur [] qui n’échoue jamais semble bien, mais peut masquer des bogues ultérieurement, si une fonction appelante utilise un décalage illégal, trouve une valeur depuis le début du tampon et procède comme s’il s’agissait d’une valeur valide.

La meilleure méthode pour réaliser la vérification des limites serait d’ajouter une assertion.

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

Si votre code dépend déjà de bibliothèques Boost, vous pouvez utiliser BOOST_ASSERT à la place.

Si j'étais vous, je suivrais l'exemple de la stl.

Dans ce cas, std::vector fournit deux méthodes: at dont les bornes sont vérifiées et operator[] qui ne l’est pas. Cela permet au client de décider avec la version à utiliser. Je ne voudrais certainement pas utiliser le % size(), car cela cache le bogue. Cependant, la vérification des limites ajoute beaucoup de temps pour les itérations sur une grande collection. C'est pourquoi elle devrait être facultative. Bien que je sois d’accord avec d’autres affiches, cette assertion est une très bonne idée, car elle ne fera que nuire aux performances dans les versions de débogage.

Vous devriez également envisager de renvoyer des références et de fournir des versions const et non const. Voici les déclarations de fonction pour <=> :

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

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

En règle générale, si je ne suis pas sûr de savoir comment spécifier une API, je cherche des exemples de la façon dont d'autres spécifient des API similaires. De plus, chaque fois que j'utilise une API, j'essaie de juger ou de noter, trouver les éléments que j'aime et que je n'aime pas.

Merci au commentaire sur la fonctionnalité C # dans le post de Daniel Daranas, j'ai réussi à trouver une solution possible. Comme je l'ai dit dans ma question, j'utilise les bibliothèques Qt. Là, je peux utiliser QVariant. QVariant peut être défini sur un état non valide pouvant être vérifié par la fonction qui le reçoit. Donc, le code deviendrait quelque chose comme:

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

    return temp;
}

Bien sûr, cela peut potentiellement entraîner un léger surcoût dans la fonction. Une autre possibilité consiste à utiliser un modèle de paire.

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;
}

Ou je pourrais utiliser un QPair, qui a exactement les mêmes fonctionnalités et qui ferait en sorte que la STL n'ait pas besoin d'être liée.

Vous pourriez peut-être ajouter un " hors limites & "; exception à l'opérateur [] (ou au moins une assertion).

Cela devrait résoudre tous les problèmes, en particulier lors du débogage.

À moins que je ne comprenne vraiment mal quelque chose,

return point[ index % dimensions ];

ne vérifie pas du tout les limites. Cela renvoie une valeur réelle à une partie totalement différente de la ligne, ce qui rendra la détection des bogues beaucoup plus difficile.

Je voudrais soit:

  1. Lance une exception ou une assertion (bien que vous ayez dit ne pas vouloir le faire)
  2. Il suffit de déréférencer le point au-delà du tableau dans un & "; naturel &"; (c’est-à-dire que vous évitez toute vérification interne). L'avantage par rapport au% est qu'ils sont plus susceptibles (bien que non défini soit indéfini) d'obtenir & "Bizarre &"; valeurs et / ou violation d'accès

En fin de compte, l'appelant enfreint vos conditions préalables et vous pouvez faire ce que vous voulez. Mais je pense que ce sont les options les plus raisonnables.

Tenez également compte de ce que C & # 259; t & # 259; lin a parlé de l'incorporation de collections STL intégrées si cela est raisonnable.

Votre solution serait bien si vous donniez accès aux points d’une forme elliptique. Mais cela entraînera de très vilains bugs si vous l’utilisez pour des fonctions géométriques arbitraires, car vous fournissez sciemment une fausse valeur.

L’opérateur modulo fonctionne étonnamment bien pour les index de tableau - il implémente également des index négatifs (ie. point[-3] = point[dimensions - 3]). Il est facile de travailler avec cela, je recommanderais donc personnellement l’opérateur modulo, à condition que ce soit bien documenté.

Une autre option consiste à laisser l'appelant choisir la stratégie hors limites. Considérez:

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

Ensuite, vous pouvez définir plusieurs stratégies que l'appelant peut choisir. Par exemple:

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;
    }
};
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top