web-dev-qa-db-fra.com

Comment puis-je gérer les avertissements "d'incompatibilité signé/non signé" (C4018)?

Je travaille avec beaucoup de code de calcul écrit en C++ avec pour objectif des performances élevées et une surcharge de mémoire. Il utilise beaucoup les conteneurs STL (la plupart du temps vector) et effectue une itération sur ceux-ci presque dans chaque fonction.

Le code itératif ressemble à ceci:

for (int i = 0; i < things.size(); ++i)
{
    // ...
}

mais il génère l'avertissement incompatibilité signé/non signé (C4018 dans Visual Studio).

Remplacer int par un type unsigned est un problème car nous utilisons fréquemment des pragmas OpenMP, et le compteur doit donc être int.

Je suis sur le point de supprimer les (centaines de) avertissements, mais j'ai bien peur d'avoir manqué une solution élégante au problème.

Sur les itérateurs . Je pense que les itérateurs sont excellents lorsqu'ils sont appliqués aux endroits appropriés. Le code sur lequel je travaille va jamais changer les conteneurs à accès aléatoire en list ou quelque chose de ce genre (donc l'itération avec int i est déjà agnostique par le conteneur) et toujours a besoin de l'index actuel. Et tout le code supplémentaire que vous devez taper (l'itérateur lui-même et l'index) ne fait que compliquer les choses et obscurcir la simplicité du code sous-jacent.

70
Andrew T

Tout est dans votre type things.size(). Ce n’est pas int, mais size_t (il existe en C++, pas en C), ce qui correspond à un type non signé "habituel", c’est-à-dire unsigned int pour x86_32.

L'opérateur "moins" (<) ne peut pas être appliqué à deux opérandes de signe différent. Il n'existe tout simplement pas de tels opcodes, et la norme ne spécifie pas si le compilateur peut effectuer une conversion de signe implicite. Donc, il traite simplement le numéro signé comme non signé et émet cet avertissement.

Il serait correct de l'écrire comme

for (size_t i = 0; i < things.size(); ++i) { /**/ }

ou même plus vite

for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }
51
udpn

Idéalement, j'utiliserais plutôt une construction comme celle-ci:

for (std::vector<your_type>::const_iterator i = things.begin(); i != things.end(); ++i)
{
  // if you ever need the distance, you may call std::distance
  // it won't cause any overhead because the compiler will likely optimize the call
  size_t distance = std::distance(things.begin(), i);
}

Ceci a le net avantage que votre code devient soudainement indépendant du conteneur.

Et en ce qui concerne votre problème, si une bibliothèque que vous utilisez nécessite l’utilisation de int où un unsigned int conviendrait mieux, leur API est désordonnée. Quoi qu'il en soit, si vous êtes sûr que ces int sont toujours positives, vous pouvez simplement faire:

int int_distance = static_cast<int>(distance);

Ce qui précisera clairement votre intention au compilateur: cela ne vous empêchera plus de recevoir des avertissements.

12
ereOn

Si vous ne pouvez pas/ne voulez pas utiliser les itérateurs et si vous ne pouvez pas/ne voulez pas utiliser std::size_t pour l'index de la boucle, créez une fonction de conversion de .size() à int qui documente l'hypothèse et effectue la conversion de manière explicite pour désactiver l'avertissement du compilateur.

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

// When using int loop indexes, use size_as_int(container) instead of
// container.size() in order to document the inherent assumption that the size
// of the container can be represented by an int.
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);
}

Ensuite, vous écrivez vos boucles comme ceci:

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

L'instanciation de ce modèle de fonction sera presque certainement en ligne. Dans les versions de débogage, l'hypothèse sera vérifiée. Dans les versions release, ce ne sera pas le cas et le code sera aussi rapide que si vous appeliez directement size (). Aucune des versions ne produira un avertissement du compilateur, et il ne s’agit que d’une légère modification de la boucle idiomatique.

Si vous souhaitez également détecter les défaillances d'hypothèses dans la version, vous pouvez remplacer l'assertion par une instruction if qui renvoie quelque chose comme std::out_of_range("container size exceeds range of int").

Notez que cela résout à la fois la comparaison signée/non signée ainsi que le problème potentiel sizeof(int)! = = sizeof(Container::size_type). Vous pouvez laisser tous vos avertissements activés et les utiliser pour attraper de vrais bugs dans d'autres parties de votre code.

7
Adrian McCarthy

Vous pouvez utiliser:

  1. type_taille, pour supprimer les messages d'avertissement
  2. itérateurs + distance (comme le premier indice)
  3. seulement des itérateurs
  4. objet de fonction

Par exemple:

// simple class who output his value
class ConsoleOutput
{
public:
  ConsoleOutput(int value):m_value(value) { }
  int Value() const { return m_value; }
private:
  int m_value;
};

// functional object
class Predicat
{
public:
  void operator()(ConsoleOutput const& item)
  {
    std::cout << item.Value() << std::endl;
  }
};

void main()
{
  // fill list
  std::vector<ConsoleOutput> list;
  list.Push_back(ConsoleOutput(1));
  list.Push_back(ConsoleOutput(8));

  // 1) using size_t
  for (size_t i = 0; i < list.size(); ++i)
  {
    std::cout << list.at(i).Value() << std::endl;
  }

  // 2) iterators + distance, for std::distance only non const iterators
  std::vector<ConsoleOutput>::iterator itDistance = list.begin(), endDistance = list.end();
  for ( ; itDistance != endDistance; ++itDistance)
  {
    // int or size_t
    int const position = static_cast<int>(std::distance(list.begin(), itDistance));
    std::cout << list.at(position).Value() << std::endl;
  }

  // 3) iterators
  std::vector<ConsoleOutput>::const_iterator it = list.begin(), end = list.end();
  for ( ; it != end; ++it)
  {
    std::cout << (*it).Value() << std::endl;
  }
  // 4) functional objects
  std::for_each(list.begin(), list.end(), Predicat());
}
6
siquell

Je vais vous donner une meilleure idée

for(decltype(things.size()) i = 0; i < things.size(); i++){
                   //...
}

decltype est

Inspecte le type déclaré d'une entité ou le type et la catégorie de valeur d'une expression.

Donc, il en déduit que le type de things.size() et i sera du même type que things.size(). Donc, i < things.size() sera exécuté sans avertissement

3
Daniel

Je peux également proposer la solution suivante pour C++ 11. 

for (auto p = 0U; p < sys.size(); p++) {

}

(C++ n'est pas assez intelligent pour auto p = 0, je dois donc mettre p = 0U ....)

2
Stepan Yakovenko

J'avais un problème similaire. Utiliser size_t ne fonctionnait pas. J'ai essayé l'autre qui a fonctionné pour moi. (comme ci-dessous)

for(int i = things.size()-1;i>=0;i--)
{
 //...
}
1
Karthik_elan

Je voudrais juste faire

int pnSize = primeNumber.size();
for (int i = 0; i < pnSize; i++)
    cout << primeNumber[i] << ' ';
0
Don Larynx