Pracuję z wieloma kodami obliczeniowymi napisanymi w C ++ z myślą o wysokiej wydajności i niskim zużyciu pamięci. Używa (głównie vector
) kontenerów STL i iteruje je prawie w każdej funkcji.
Kod iteracyjny wygląda następująco:
for (int i = 0; i < things.size(); ++i)
{
// ...
}
ale generuje ostrzeżenie o niezgodności podpisanej / niepodpisanej (C4018 w programie Visual Studio).
Zastąpienie int
jakimś unsigned
typem jest problemem, ponieważ często używamy pragm OpenMP i wymaga to, aby licznik był int
.
Mam zamiar stłumić (setki) ostrzeżeń, ale obawiam się, że przegapiłem jakieś eleganckie rozwiązanie problemu.
Na iteratorach . Myślę, że iteratory są świetne, gdy są stosowane w odpowiednich miejscach. Kod, z którym pracuję, nigdy nie zmieni kontenerów o dostępie swobodnym na list
lub coś (więc iteracja z użyciem int i
jest już agnostyczna) i zawsze będzie potrzebować bieżącego indeksu. A cały dodatkowy kod, który trzeba wpisać (sam iterator i indeks) tylko komplikuje sprawę i zaciemnia prostotę kodu bazowego.
źródło
int
.int
istd::vector<T>::size_type
może również różnić się rozmiarem i podpisem. Na przykład w systemie LLP64 (takim jak 64-bitowy system Windows),sizeof(int) == 4
alesizeof(std::vector<T>::size_type) == 8
.Odpowiedzi:
Wszystko jest w twoim
things.size()
typie. Nie jestint
, alesize_t
(istnieje w C ++, nie w C), co jest równeunsigned int
pewnemu "zwykłemu" typowi bez znaku, np. Dla x86_32.Operator „mniej” (<) nie może być zastosowany do dwóch operandów o różnych znakach. Po prostu nie ma takich rozkazów, a standard nie określa, czy kompilator może dokonać niejawnej konwersji znaku. Więc po prostu traktuje podpisany numer jako niepodpisany i wysyła to ostrzeżenie.
Byłoby dobrze napisać to w ten sposób
for (size_t i = 0; i < things.size(); ++i) { /**/ }
lub nawet szybciej
for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }
źródło
size_t
. Tak jeststd::vector< THING >::size_type
.std::size_t
istd::vector<T>::size_type
.Idealnie, zamiast tego użyłbym takiej konstrukcji:
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); }
Ma to tę zaletę, że kod nagle staje się agnostykiem kontenera.
A jeśli chodzi o twój problem, jeśli jakaś biblioteka, której używasz, wymaga, abyś używał
int
tam, gdzieunsigned int
byłoby lepiej, ich API jest brudne. W każdym razie, jeśli jesteś pewien, żeint
są one zawsze pozytywne, możesz po prostu zrobić:int int_distance = static_cast<int>(distance);
Który jasno określi twoje zamiary dla kompilatora: nie będzie już powodował błędów z ostrzeżeniami.
źródło
static_cast<int>(things.size())
mogłyby to być rozwiązania, gdyby nie było innych.#pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)
) zamiast użycia niepotrzebnego rzutowania. (Casts ukrywa uzasadnione błędy, m'kay?;))Jeśli nie może / nie używać iteratorów a jeśli nie może / nie używać
std::size_t
do indeksu pętli zrobić.size()
doint
funkcji konwersji, że dokumenty założeniu i czy konwersja wyraźnie uciszyć ostrzeżenie kompilatora.#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); }
Następnie piszesz swoje pętle w ten sposób:
for (int i = 0; i < size_as_int(things); ++i) { ... }
Instancja tego szablonu funkcji będzie prawie na pewno wbudowana. W kompilacjach debugowania założenie zostanie sprawdzone. W kompilacjach wydań nie będzie, a kod będzie tak szybki, jak w przypadku bezpośredniego wywołania size (). Żadna wersja nie wyświetli ostrzeżenia kompilatora i jest to tylko niewielka modyfikacja pętli idiomatycznej.
Jeśli chcesz wychwycić błędy założeń również w wydanej wersji, możesz zamienić potwierdzenie na instrukcję if, która generuje coś podobnego
std::out_of_range("container size exceeds range of int")
.Zauważ, że rozwiązuje to zarówno porównanie ze znakiem / bez znaku, jak i potencjalny problem
sizeof(int)
! =sizeof(Container::size_type)
. Możesz pozostawić wszystkie ostrzeżenia włączone i użyć ich do wyłapywania prawdziwych błędów w innych częściach kodu.źródło
Możesz użyć:
Na przykład:
// 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()); }
źródło
Mogę również zaproponować następujące rozwiązanie dla C ++ 11.
for (auto p = 0U; p < sys.size(); p++) { }
(C ++ nie jest wystarczająco inteligentny dla auto p = 0, więc muszę umieścić p = 0U ....)
źródło
for (auto thing : vector_of_things)
jeśli faktycznie nie potrzebujesz indeksu.size()
zwraca typ większy niż unsigned int, co jest niezwykle powszechne.Dam ci lepszy pomysł
for(decltype(things.size()) i = 0; i < things.size(); i++){ //... }
decltype
jestWięc wydedukuje typ
things.size()
ii
będzie typem takim samym jakthings.size()
. Więci < things.size()
zostanie wykonany bez żadnego ostrzeżeniaźródło
Miałem podobny problem. Używanie size_t nie działało. Wypróbowałem inny, który działał dla mnie. (jak poniżej)
for(int i = things.size()-1;i>=0;i--) { //... }
źródło
Po prostu bym to zrobił
int pnSize = primeNumber.size(); for (int i = 0; i < pnSize; i++) cout << primeNumber[i] << ' ';
źródło