Я сам убежден, что в проекте, над которым я работаю, целые числа со знаком являются лучшим выбором в большинстве случаев, даже если значение, содержащееся в нем, никогда не может быть отрицательным. (Упрощенный реверс для циклов, меньше шансов для ошибок и т. Д., В частности, для целых чисел, которые в любом случае могут содержать значения только от 0 до, скажем, 20).
Большинство мест, где это идет не так, - это простая итерация std :: vector, часто в прошлом это был массив, который позже был заменен на std :: vector. Таким образом, эти циклы обычно выглядят так:
for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }
Поскольку этот шаблон используется так часто, количество спама, предупреждающего компилятор об этом сравнении между типом со знаком и без знака, имеет тенденцию скрывать более полезные предупреждения. Обратите внимание, что у нас определенно нет векторов с более чем элементами INT_MAX, и обратите внимание, что до сих пор мы использовали два способа исправить предупреждение компилятора:
for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }
Обычно это работает, но может молча прерываться, если цикл содержит какой-либо код, например, 'if (i-1> = 0) ...' и т. Д.
for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { /*do stuff*/ }
Это изменение не имеет побочных эффектов, но делает цикл намного менее читабельным. (И это больше печатать.)
Итак, мне пришла в голову следующая идея:
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
То, что вы видите, - это, в основном, классы STL с методами, которые возвращают size_type, переопределенный, чтобы возвращать просто 'int'. Конструкторы необходимы, потому что они не наследуются.
Что бы вы подумали об этом как о разработчике, если бы увидели подобное решение в существующей базе кода?
Не могли бы вы подумать: «да, они переопределяют STL, какой огромный WTF!», Или вы думаете, что это хорошее простое решение для предотвращения ошибок и повышения читабельности. Или, может быть, вы бы предпочли, чтобы мы потратили (половину) дня или около того на изменение всех этих циклов для использования std :: vector <> :: iterator?
(В частности, если это решение было объединено с запретом использования типов без знака для чего-либо, кроме необработанных данных (например, без знака) и битовых масок.)