可接受的解决对于大多数的签署/unsigned警告?
-
07-07-2019 - |
题
我自己也相信,在一个项目,我正在签署整数是最好的选择,在大多数情况下,即使该价值内所包含的不可以是负面的。(简单反对的循环,较少机会的错误,等等, 特别是对于整数,这可能只值在0到20,无论如何。)
大多数地方,这种错误是一个简单的迭代的一个标准::矢量,这往往使用的是一系列在过去已经被改变为一种标准::矢量之后。因此,这些循环通常是这样的:
for (int i = 0; i < someVector.size(); ++i) { /* do stuff */ }
因为这种模式经常使用量的编译器的警告垃圾邮件有关这个之间的比较签署和未签名类型倾向于隐藏更多的有益的警告。注意,我们绝对没有向量超INT_MAX元,并注意到现在为止我们使用两种方式来解决编译器的警告:
for (unsigned i = 0; i < someVector.size(); ++i) { /*do stuff*/ }
这通常的工作,但可能会默默地打破,如果循环中包含任何这样的代码,如果(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'.建设是必要的,因为这些不是继承。
你会怎么认为的这个作为开发者,如果你会看到一个解决方案这样的一个现有基本代码?
你会认为'whaa,他们正在重新界定STL,什么一个巨大的跆拳道!', 或者你认为这是一个很简单的解决方案,以防止错误,并增加可读性。或许你宁愿看到我们已经花了(一半)的一天或以上改变所有这些循环使用std::矢量<>::迭代?
(尤其是如果这一解决方案是结合禁止使用未签名类型的任何东西,但原始数据(例如unsigned char)和位的面具。)
解决方案
我做了这个社区wiki...请编辑。我不同意的建议对"int"了。我现在看到它作为不坏。
是的,我同意与理查德。你不应该使用 'int'
作为该计数变量,在一个循环喜欢的那些。以下是你怎么可能想要做的各种循环使用指数(虽然没有理由,偶尔这可能是有用的)。
向前
for(std::vector<int>::size_type i = 0; i < someVector.size(); i++) {
/* ... */
}
向后
你可以做到这一点,这是完全定义behaivor:
for(std::vector<int>::size_type i = someVector.size() - 1;
i != (std::vector<int>::size_type) -1; i--) {
/* ... */
}
很快,用c++1(下C++版)起来很好,你可以做像这样:
for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
/* ... */
}
减低0将导致我到包围,这是因为它无符号。
但unsigned会让虫子吃中
这不应该是一个参数作了错误的方式(使用 'int'
).
为什么不使用std::位置上?
C++的标准定义中 23.1 p5 Container Requirements
, 那 T::size_type
, T
被一些 Container
, ,这种类型的某些定义的实现无符号组成的类型。现在,使用 std::size_t
对于 i
上让虫子吃在默默。如果 T::size_type
小的或大于 std::size_t
, 然后它将溢出 i
, 或甚至得到了 (std::size_t)-1
如果 someVector.size() == 0
.同样,条件的循环就会已经被打破。
其他提示
不得公开从STL容器。他们必须确定其析构,它援引不确定的行为如果有人删除了你的一个目的通过的指针为基础。如果你必须得出如从一矢量,做到这一私下和获得的零部件需要获得有 using
声明。
在这里,我只是用一个 size_t
作为的循环变量。这是简单易读。海报人评论说,使用 int
指数公开你作为一个n00b是正确的。但是,使用一个迭代的循环过矢量暴露了你作为一个稍微更有经验的n00b-一个人不意识到,标操作人员为矢量为恒定时间。(vector<T>::size_type
是准确的,但是不必要的冗长的海事组织).
虽然我不认为"使用的迭代,否则你看起来n00b"是一个很好的解决问题的办法,来自std::矢量出现多少比这还要糟糕。
第一,开发商不要期望矢量是std:.矢量,以及地图是std::图。第二,你的解决方案不会规模的其他容器,或用于其他类/图书馆,与集装箱。
是的,迭代都是丑陋的,迭代的循环不是非常好的可读性、和类型定义仅涵盖了混乱。但至少,他们做的比例和它们的规范的解决方案。
我的解决方案吗?stl为每个宏。这不是没有问题(主要是,它是一个宏呸),但它得到的全的含义。它不是一样先进,因为例如 这一个, 但不会的工作。
肯定使用一个迭代器。你很快就将能够使用的"自动"型,用于更好的可读性(你的一个问题)是这样的:
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) { ... }
你也可以使用 std::size_t
(从 <cstddef>).有些人(正确地)指出, std::size_t
可能不是同一类型 std::vector<T>::size_type
(尽管它通常是)。但是,你可以放心,容器的 size_type
将适合 std::size_t
.所以一切都是好的,除非你使用的某些款式进反循环。我喜欢的风格一反循环是这样的:
for (std::size_t i = v.size(); i-- > 0; ) { ... }
与这种风格的,你可以安全使用 std::size_t
, 即使这是一个较大的种类比 std::vector<T>::size_type
.风格的反向环示出在一些其他的答案需要投-1到完全正确的类型,因此不能使用的更简单的类型 std::size_t
.
使用的签名类型(小心!)
如果你真的想要使用的签名类型(或者如果你的 风格的指南的实际需求之一),喜欢 int
, 然后你可以用这个小小功能的模板,用于检查的基本假设在调试建立和使转换的明确这样你就不会得到编译器的警告信息:
#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) { ... }
的 size_as_int
招的只是稍微更多打字比循环的隐性转换,你得到的基本假设检查在运行时,你的沉默所警告编译器的明确铸造的,你得到同样的速度作为非调试版本,因为它几乎肯定会内联,并优化的目标代码不应该有任何较大,因为该模板什么也不做编译是不是已经隐含地做.
你是想太多的问题。
使用一个位置的变量是可取的,但是如果你不相信你的程序员可以使用无符号正确,去演员和只是处理丑陋。得到一个实习生改变他们的所有而不要担心它。打开警告视为错误的和没有新的会蠕变。你的循环可能是"丑陋的"现在,但是可以理解的是,如果你的宗教立场,签署了与无符号。
vector.size()
返回 size_t
var,所以只需改变 int
要 size_t
它应该被罚款。
理查德的答案是比较正确的,但它是一个很大的工作一简单的循环。