C++ 大多数已签名/未签名警告的可接受修复程序?

C++ 大多数已签名/未签名警告的可接受修复程序?,c++,stl,coding-style,unsigned,C++,Stl,Coding Style,Unsigned,我自己确信,在大多数情况下,在我从事的项目中,有符号整数是最佳选择,即使其中包含的值永远不能为负。(循环的反转更简单,出现错误的机会更小,等等,特别是对于只能保持0到20之间的值的整数。) 大多数出错的地方都是std::vector的简单迭代,过去通常是数组,后来改为std::vector。因此,这些循环通常如下所示: for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ } for(auto i = someVecto

我自己确信,在大多数情况下,在我从事的项目中,有符号整数是最佳选择,即使其中包含的值永远不能为负。(循环的反转更简单,出现错误的机会更小,等等,特别是对于只能保持0到20之间的值的整数。)

大多数出错的地方都是std::vector的简单迭代,过去通常是数组,后来改为std::vector。因此,这些循环通常如下所示:

for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }
for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
    /* ... */
}
for (auto i = someVector.begin();
     i != someVector.end();
     ++i)
for(int i=0;i
由于经常使用此模式,有关有符号和无符号类型之间比较的编译器警告垃圾邮件的数量往往会隐藏更多有用的警告。请注意,我们绝对没有包含多于INT_MAX元素的向量,并且请注意,到目前为止,我们使用了两种方法来修复编译器警告:

for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }
for(无符号i=0;i
这通常有效,但如果循环包含诸如“if(i-1>=0)…”等任何代码,则可能会自动中断

for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { /*do stuff*/ }
for(int i=0;i
此更改没有任何副作用,但它确实使循环的可读性大大降低。(而且更多的是打字。)

所以我想到了以下想法:

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
模板结构向量:公共std::vector { typedef std::向量基; int size()常量{return base::size();} int max_size()常量{return base::max_size();} int capacity()常量{return base::capacity();} 向量():基(){} 向量(int n):基(n){} 向量(int n,const T&T):基(n,T){ 向量(常数基和其他):基(其他){} }; 模板结构映射:公共标准::映射 { typedef std::地图库; typedef typename base::key\u compare key\u compare; int size()常量{return base::size();} int max_size()常量{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_comp&comp):base(comp){ 模板映射(输入计算器f,输入计算器l):基(f,l){} 模板映射(输入迭代器f、输入迭代器l、常量键\u比较和比较):基(f、l、比较){} 映射(常量基和其他):基(其他){} }; //TODO:其他容器类型的类似代码
您看到的基本上是STL类,它们的方法返回size\u type,并被重写为只返回“int”。构造函数是必需的,因为它们不是继承的

如果您在现有的代码库中看到这样的解决方案,作为一名开发人员,您会怎么想

你会认为‘哇,他们正在重新定义STL,多么巨大的WTF!’,或者你会认为这是一个很好的简单的解决方案,以防止错误和增加可读性。或者您更愿意看到我们花了(半天)左右的时间来更改所有这些循环,以使用std::vector::iterator

(特别是如果将此解决方案与禁止对原始数据(例如,无符号字符)和位掩码以外的任何内容使用无符号类型结合使用的话。)

我创建了此社区wiki。。。请编辑它。我不再同意反对“int”的建议。我现在觉得还不错。 是的,我同意理查德的观点。在这样的循环中,决不能使用
'int'
作为计数变量。下面是您可能希望如何使用索引执行各种循环(尽管没有什么理由这样做,但有时这可能很有用)

向前地 递减到0以下将导致i换行,因为它是无符号的

但未签名会让虫子发出咕噜声 这永远不应该是一个错误的参数(使用
'int'

为什么不使用上面的std::size\u t?
< C++ >标准定义在<代码> 23.1 p5容器要求<代码>中,<<代码> t::siZeHype类型< /> >,对于<代码> t>代码>是一些<代码>容器< /代码>,该类型是一些实现定义的无符号整数类型。现在,对上面的
i
使用
std::size\t
会让bug悄悄地潜入。如果
T::size\u type
小于或大于
std::size\u T
,则它将溢出
i
,或者如果
someVector.size()==0
,甚至达不到
(std::size\u T)-1
。同样,循环的条件也会被完全破坏。

不要公开地从STL容器派生。它们有非虚拟析构函数,如果有人通过指向base的指针删除您的一个对象,它将调用未定义的行为。如果您必须派生(例如,从向量派生),请私下进行,并使用
声明公开需要公开的部分

在这里,我只使用
size\u t
作为循环变量。它简单易读。海报评论说使用
int
索引将您暴露为n00b是正确的。然而,使用迭代器在向量上循环会使您暴露为一个稍有经验的n00b—一个没有意识到向量的下标操作符是常量时间的人。(
vector::size\u type
是准确的,但在IMO中不必要地冗长)。

vector.size()
返回一个
size\u t
变量,所以只需将
int
更改为
size\u t
,就可以了


Richard的答案更正确,只是简单的循环需要做大量的工作。

你想得太多了

使用size\u t变量是更好的选择,但若你们不相信你们的程序员能正确地使用unsigned,那个么就用cast来处理它的丑陋。找个实习生把它们全部换掉,之后就不用担心了。将警告作为错误打开,不会有新的警告出现。你的循环现在可能是“丑陋”的,但你可以理解这是你的宗教立场对签名与非签名的后果
for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
    /* ... */
}
for (auto i = someVector.begin();
     i != someVector.end();
     ++i)
for (auto it = begin(v); it != end(v); ++it) { ... }
for (const auto &x : v) { ... }
std::for_each(v.begin(), v.end(), ...);
for (std::vector<T>::size_type i = 0; i < v.size(); ++i) { ... }
for (std::size_t i = v.size(); i-- > 0; ) { ... }
#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);
}
for (int i = 0; i < size_as_int(v); ++i) { ... }
for (int i = size_as_int(v) - 1; i >= 0; --i) { ... }