Hoe ga ik om met waarschuwingen voor ‘ondertekende/niet-ondertekende mismatch’ (C4018)?

Ik werk met veel rekencode geschreven in C++ met hoge prestaties en lage geheugenoverhead in gedachten. Het gebruikt veel STL-containers (meestal vector) en herhaalt die containers bijna in elke afzonderlijke functie.

De herhalende code ziet er als volgt uit:

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

maar het produceert de waarschuwing ondertekend/niet-ondertekend niet-overeenkomend(C4018 in Visual Studio).

Het vervangen van intdoor een unsignedtype is een probleem omdat we vaak OpenMP-pragma’s gebruiken, en het vereist dat de teller intis.

Ik sta op het punt de (honderden) waarschuwingen te onderdrukken, maar ik ben bang dat ik een elegante oplossing voor het probleem heb gemist.

Op iterators. Ik denk dat iterators geweldig zijn als ze op de juiste plaatsen worden toegepast. De code waarmee ik werk zal nooitwillekeurig toegankelijke containers veranderen in listof zoiets (dus herhalen met int iis al container-agnostisch) , en heeft altijdde huidige index nodig. En alle extra code die je moet typen (iterator zelf en de index) maakt de zaken alleen maar ingewikkelder en vertroebelt de eenvoud van de onderliggende code.


Antwoord 1, autoriteit 100%

Het zit allemaal in je things.size()-type. Het is niet int, maar size_t(het bestaat in C++, niet in C) wat overeenkomt met een “gebruikelijk” niet-ondertekend type, dat wil zeggen unsigned intvoor x86_32.

Operator “Minder” (& LT;) kan niet worden toegepast op twee operanden van een ander teken. Er zijn gewoon geen opcodes en standaard geeft niet op of compiler impliciete tekenconversie kan maken. Het heeft dus gewoon getekend nummer behandeld als niet-ondertekend en zendt die waarschuwing.

het zou correct zijn om het te schrijven als

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

of zelfs sneller

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

2, Autoriteit 21%

Idealiter zou ik in plaats daarvan een constructie als deze gebruiken:

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

Dit A heeft het nette voordeel dat uw code plotseling container Agnostic wordt.

en met betrekking tot uw probleem, als sommige bibliotheek u gebruikt, moet u gebruiken intwaar een unsigned intBetter past, hun API is rommelig. Hoe dan ook, als u zeker weet dat die intaltijd positief zijn, kunt u gewoon doen:

int int_distance = static_cast<int>(distance);

die duidelijk uw intentie op de compiler zal specificeren: het zal u niet meer inruilen met waarschuwingen.


3, Autoriteit 14%

Als je geen iterators kunt/wilt gebruiken en als je std::size_tniet kunt/wilt gebruiken voor de loop-index, maak dan een .size()naar intconversiefunctie die de aanname documenteert en de conversie expliciet uitvoert om de compilerwaarschuwing tot zwijgen te brengen.

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

Vervolgens schrijf je je loops als volgt:

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

De instantie van deze functiesjabloon zal vrijwel zeker inline zijn. In debug-builds wordt de aanname gecontroleerd. In release-builds is dit niet het geval en is de code net zo snel als wanneer u size() rechtstreeks aanroept. Geen van beide versies zal een compilerwaarschuwing produceren, en het is slechts een kleine wijziging aan de idiomatische lus.

Als je aannamefouten ook in de releaseversie wilt opvangen, kun je de bewering vervangen door een if-statement dat zoiets als std::out_of_range("container size exceeds range of int").

Merk op dat dit zowel de ondertekende / niet-ondertekende vergelijking als de potentiële sizeof(int)! = sizeof(Container::size_type)probleem gebruiken. U kunt al uw waarschuwingen binnenlaten en gebruiken om echte bugs in andere delen van uw code te vangen.


4, Autoriteit 10%

U kunt gebruiken:

  1. size_t-type, om waarschuwingsberichten te verwijderen
  2. iterators + afstand (zoals eerste hint)
  3. Alleen iterators
  4. functieobject

Bijvoorbeeld:

// 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());
}

5, Autoriteit 5%

Ik kan ook de volgende oplossing voor C++ 11 voorstellen.

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

(C++ is niet slim genoeg voor auto p = 0, dus ik moet p = 0u ….)


6

Ik had een soortgelijk probleem. Het gebruik van Size_t werkte niet. Ik probeerde de andere die voor mij werkte. (zoals hieronder)

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

7

Ik zou gewoon doen

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

8

C++ 20 heeft nu std::cmp_less

in C++ 20 hebben we de standaard constexprFuncties

std::cmp_equal
std::cmp_not_equal
std::cmp_less
std::cmp_greater
std::cmp_less_equal
std::cmp_greater_equal

toegevoegd in de <utility>header, precies voor dit soort scenario’s.

Vergelijk de waarden van twee gehele getallen ten u. In tegenstelling tot ingebouwde vergelijkingsoperatoren, vergelijken gehele getallen met negatief teken altijd minder dan (en niet gelijk aan) gehele getallen zonder teken: de vergelijking is veilig tegen conversie van gehele getallen met verlies.

Dat betekent dat als men (vanwege een aantal bedrade redenen) de imoet gebruiken als integer, de lussen, en moet worden vergeleken met het niet-ondertekende gehele getal, dat kan worden gedaan:

#include <utility> // std::cmp_less
for (int i = 0; std::cmp_less(i, things.size()); ++i)
{
    // ...
}

Dit dekt ook het geval, als we per ongeluk static_castde -1(dwz int)naar unsigned int. Dat betekent dat het volgende u geen foutmelding geeft:

static_assert(1u < -1);

Maar het gebruik van std::cmp_lesszal

static_assert(std::cmp_less(1u, -1)); // error

Other episodes