Question

Je suis moi-même convaincu que dans un projet sur lequel je travaille, les entiers signés sont le meilleur choix dans la majorité des cas, même si la valeur qu'ils contiennent ne peut jamais être négative. (Inverse plus simple pour les boucles, moins de risques de bugs, etc., en particulier pour les entiers ne pouvant contenir que des valeurs comprises entre 0 et 20, de toute façon.)

La majorité des endroits où cela ne va pas est une simple itération d'un std :: vector, ce qui était souvent un tableau dans le passé et a été changé en std :: vector plus tard. Donc, ces boucles ressemblent généralement à ceci:

for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }

Étant donné que ce modèle est fréquemment utilisé, la quantité de spam d'avertissement du compilateur concernant cette comparaison entre les types signé et non signé tend à masquer des avertissements plus utiles. Notez que nous n'avons pas de vecteurs avec plus que des éléments INT_MAX, et notons que jusqu'à présent, nous utilisions deux méthodes pour résoudre l'avertissement du compilateur:

for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }

Cela fonctionne généralement, mais peut se rompre si la boucle contient un code tel que 'if (i-1 > = 0) ...', etc.

for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { /*do stuff*/ }

Cette modification n’a aucun effet secondaire, mais elle rend la boucle beaucoup moins lisible. (Et c'est plus en tapant.)

J'ai donc eu l'idée suivante:

template <typename T> struct vector : public std::vector<T>
{
    typedef std::vector<T> base;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }
    int capacity() const { return base::capacity(); }

    vector()                  : base() {}
    vector(int n)             : base(n) {}
    vector(int n, const T& t) : base(n, t) {}
    vector(const base& other) : base(other) {}
};

template <typename Key, typename Data> struct map : public std::map<Key, Data>
{
    typedef std::map<Key, Data> base;
    typedef typename base::key_compare key_compare;

    int size() const     { return base::size(); }
    int max_size() const { return base::max_size(); }

    int erase(const Key& k) { return base::erase(k); }
    int count(const Key& k) { return base::count(k); }

    map() : base() {}
    map(const key_compare& comp) : base(comp) {}
    template <class InputIterator> map(InputIterator f, InputIterator l) : base(f, l) {}
    template <class InputIterator> map(InputIterator f, InputIterator l, const key_compare& comp) : base(f, l, comp) {}
    map(const base& other) : base(other) {}
};

// TODO: similar code for other container types

Ce que vous voyez, ce sont essentiellement les classes STL avec les méthodes qui renvoient size_type, afin de ne renvoyer que 'int'. Les constructeurs sont nécessaires car ils ne sont pas hérités.

Que pensez-vous de ce développeur, si vous voyez une telle solution dans une base de code existante?

Pensez-vous que "whaa, ils redéfinissent le STL, quel énorme WTF!", ou pensez-vous qu'il s'agit d'une solution simple et agréable pour éviter les bugs et améliorer la lisibilité. Ou peut-être préféreriez-vous voir que nous avions passé (une demi-journée) environ à changer toutes ces boucles pour utiliser std :: vector & Lt; & Gt; :: iterator?

(En particulier, si cette solution était associée à l'interdiction d'utiliser des types non signés pour des données autres que les données brutes (par exemple, les caractères non signés) et les masques binaires.)

Était-ce utile?

La solution

J'ai créé ce wiki de communauté ... Veuillez le modifier. Je ne suis pas d'accord avec le conseil contre & Quot; int & Quot; plus. Je le vois maintenant comme pas mal.

Oui, je suis d'accord avec Richard. Vous ne devez jamais utiliser 'int' comme variable de comptage dans une boucle comme celles-ci. Vous trouverez ci-dessous comment créer différentes boucles à l'aide d'indices (bien qu'il y ait peu de raisons de le faire, cela peut parfois être utile).

Transférer

for(std::vector<int>::size_type i = 0; i < someVector.size(); i++) {
    /* ... */
}

En arrière

Vous pouvez le faire, ce qui est parfaitement défini comme comportement:

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* ... */
}

Bientôt, avec c ++ 1x (version C ++ suivante), vous pouvez le faire comme ceci:

for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
    /* ... */
}

Une décrémentation en dessous de 0 entraînera un rebouclage de i, car elle est non signée.

Mais unsigned fera que les bugs se faufileront dans

Cela ne devrait jamais être un argument pour le rendre incorrect (avec 23.1 p5 Container Requirements).

Pourquoi ne pas utiliser std :: size_t ci-dessus?

La norme C ++ définit dans T::size_type, que T, pour Container être quelque <<>, ce type est un type intégral non signé défini par la mise en oeuvre. Maintenant, utiliser std::size_t pour i ci-dessus laissera les bugs se glisser dans le silence. Si (std::size_t)-1 est inférieur ou supérieur à someVector.size() == 0, il débordera <=>, ou ne se lèvera même pas à <=> si <=>. De même, l'état de la boucle aurait été complètement cassé.

Autres conseils

Ne dérivez pas publiquement des conteneurs STL. Ils ont des destructeurs non virtuels qui invoquent un comportement indéfini si quelqu'un supprime l'un de vos objets via un pointeur sur la base. Si vous devez en déduire par exemple depuis un vecteur, faites-le en privé et exposez les pièces à exposer avec using déclarations.

Ici, je voudrais juste utiliser un size_t comme variable de boucle. C'est simple et lisible. L’affiche qui a commenté que l’utilisation d’un index int vous expose en tant que n00b est correcte. Cependant, utiliser un itérateur pour boucler sur un vecteur vous expose comme un n00b légèrement plus expérimenté - quelqu'un qui ne se rend pas compte que l'opérateur en indice du vecteur est un temps constant. (vector<T>::size_type est exact, mais inutilement bavard, OMI).

Bien que je ne pense pas & "utiliser des itérateurs, sinon vous regardez n00b &"; est une bonne solution au problème, dériver de std :: vector semble bien pire que cela.

Premièrement, les développeurs s'attendent à ce que vector soit std: .vector, et map à std :: map. Deuxièmement, votre solution ne s'adapte pas aux autres conteneurs, ni aux autres classes / bibliothèques qui interagissent avec les conteneurs.

Oui, les itérateurs sont laids, les boucles d’itérateurs ne sont pas très lisibles, et les modifications de type ne masquent que le désordre. Mais au moins, ils font l’échelle, et ils sont la solution canonique.

Ma solution? une macro stl-for-each. Ce n'est pas sans problèmes (principalement, c'est une macro, beurk), mais ça passe à travers le sens. Ce n'est pas aussi avancé que par exemple celui-ci , mais fait le travail.

Utilisez certainement un itérateur. Bientôt, vous pourrez utiliser le type "auto", pour une meilleure lisibilité (une de vos préoccupations), comme ceci:

for (auto i = someVector.begin();
     i != someVector.end();
     ++i)

Ignorer l'index

La solution la plus simple consiste à contourner le problème en utilisant des itérateurs, des boucles for basées sur la plage ou des algorithmes:

for (auto it = begin(v); it != end(v); ++it) { ... }
for (const auto &x : v) { ... }
std::for_each(v.begin(), v.end(), ...);

C'est une bonne solution si vous n'avez pas réellement besoin de la valeur d'index. Il gère également les boucles inverses facilement.

Utilisez un type non signé approprié

Une autre approche consiste à utiliser le type de taille du conteneur.

for (std::vector<T>::size_type i = 0; i < v.size(); ++i) { ... }

Vous pouvez également utiliser std::size_t (à partir de < cstddef >). Certains soulignent (correctement) que std::vector<T>::size_type peut ne pas être du même type que size_type (bien que ce soit généralement le cas). Vous pouvez toutefois être assuré que le int conteneur contiendra un size_as_int. Donc tout va bien, sauf si vous utilisez certains styles pour les boucles inversées. Mon style préféré pour une boucle inversée est le suivant:

for (std::size_t i = v.size(); i-- > 0; ) { ... }

Avec ce style, vous pouvez utiliser en toute sécurité <=>, même s'il s'agit d'un type plus volumineux que <=>. Le style des boucles inversées présenté dans certaines des autres réponses nécessite de transtyper -1 avec exactement le bon type et ne peut donc pas utiliser le plus facile à taper <=>.

Utilisez un type signé (avec soin!)

Si vous souhaitez vraiment utiliser un type signé (ou si votre exige un guide de style ), comme <=>, vous pouvez utiliser ce modèle de fonction minuscule qui vérifie l'hypothèse sous-jacente dans les versions de débogage et rend la conversion explicite afin d'éviter l'avertissement du compilateur message:

#include <cassert>
#include <cstddef>
#include <limits>

template <typename ContainerType>
constexpr int size_as_int(const ContainerType &c) {
    const auto size = c.size();  // if no auto, use `typename ContainerType::size_type`
    assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
    return static_cast<int>(size);
}

Vous pouvez maintenant écrire:

for (int i = 0; i < size_as_int(v); ++i) { ... }

Ou inversez les boucles de la manière traditionnelle:

for (int i = size_as_int(v) - 1; i >= 0; --i) { ... }

L'astuce <=> consiste à taper légèrement plus que les boucles avec les conversions implicites, vous obtenez l'hypothèse sous-jacente vérifiée au moment de l'exécution, vous désactivez l'avertissement du compilateur avec la distribution explicite, vous obtenez la même vitesse que les versions sans débogage car il sera presque certainement en ligne, et le code objet optimisé ne devrait pas être plus volumineux, car le modèle ne fait rien que le compilateur ne faisait pas déjà implicitement.

Vous pensez trop au problème.

Il est préférable d’utiliser une variable size_t, mais si vous ne croyez pas que vos programmeurs utilisent correctement la signature non signée, utilisez le casting et traitez simplement la laideur. Obtenez un stagiaire pour les changer tous et ne vous inquiétez pas pour cela après cela. Activez les avertissements, car aucune erreur ne se créera. Vos boucles peuvent être & "; Moche &"; maintenant, mais vous pouvez comprendre cela comme les conséquences de votre position religieuse sur le fait de signer par rapport à non signé.

vector.size() retourne une size_t var, il suffit donc de changer int en <=> et tout devrait bien se passer.

La réponse de Richard est plus correcte, sauf que cela demande beaucoup de travail pour une simple boucle.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top