代码之家  ›  专栏  ›  技术社区  ›  Tobi

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

  •  9
  • Tobi  · 技术社区  · 16 年前

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

    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\u type,并被重写为只返回“int”。构造函数是必需的,因为它们不是继承的。

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

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

    7 回复  |  直到 16 年前
        1
  •  7
  •   fizzer    16 年前

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

    这里,我就用一个 size_t int 索引显示n00b是正确的。然而,使用迭代器在向量上循环会使您暴露为一个稍有经验的n00b—一个没有意识到向量的下标操作符是常量时间的人。( vector<T>::size_type 是准确的,但不必要的冗长)。

        2
  •  4
  •   peterchen    16 年前

    是的,迭代器很难看,迭代器循环的可读性不是很好,typedef只能掩盖混乱。但至少,它们是可伸缩的,它们是标准解。

    我的解决方案?每个宏的stl。这不是没有问题的(主要是一个宏观的,恶心),但它理解了它的含义。它不如例如。 this one ,但这项工作。

        3
  •  3
  •   Tim Weiler    16 年前

    for (auto i = someVector.begin();
         i != someVector.end();
         ++i)
    
        4
  •  3
  •   Johannes Schaub - litb    11 年前

    我做了这个社区维基。。。请编辑它。我不再同意反对“int”的建议。我现在觉得还不错。

    'int' 作为循环中的计数变量。下面是您可能希望如何使用索引执行各种循环(尽管没有什么理由这样做,但有时这可能很有用)。

    for(std::vector<int>::size_type i = 0; i < someVector.size(); i++) {
        /* ... */
    }
    

    向后的

    您可以这样做,这是完美定义的行为:

    for(std::vector<int>::size_type i = someVector.size() - 1; 
        i != (std::vector<int>::size_type) -1; i--) {
        /* ... */
    }
    

    很快,用C++ 1x(下一个C++版本)很好地实现,你可以这样做:

    for(auto i = someVector.size() - 1; i != (decltype(i)) -1; i--) {
        /* ... */
    }
    

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

    这永远不应该成为一个错误的理由(使用 “int”

    为什么不使用上面的std::size\u t?

    23.1 p5 Container Requirements 那个 T::size_type 对于 T Container ,该类型是某个实现定义的无符号整数类型。现在,使用 std::size_t i 标准:尺寸 ,则会溢出 ,或甚至没有达到 (std::size_t)-1 如果 someVector.size() == 0 . 同样,循环的条件也会被完全破坏。

        5
  •  3
  •   Adrian McCarthy    11 年前

    跳过索引

    最简单的方法是使用迭代器、基于范围的for循环或算法来回避问题:

    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 (摘自<cstdef>)。有人(正确地)指出 标准:尺寸 可能与的类型不同 std::vector<T>::size_type size_type 将适合于 标准:尺寸 . 所以一切都很好,除非您使用某些样式进行反向循环。我喜欢的反向循环样式是:

    for (std::size_t i = v.size(); i-- > 0; ) { ... }
    

    标准:尺寸 . 其他一些答案中显示的反向循环的样式要求将a-1转换为完全正确的类型,因此无法使用更容易的类型 标准:尺寸 .

    使用签名类型(小心!)

    style guide practically demands one ),像 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 技巧只比隐式转换的循环稍微多一些类型,在运行时检查基本假设,用显式转换消除编译器警告,获得与非调试构建相同的速度,因为它几乎肯定是内联的,而且优化的目标代码不应该太大,因为模板没有做任何编译器尚未隐式完成的事情。

        6
  •  0
  •   Dan Olson    16 年前

    使用size\u t变量是更好的选择,但若你们不相信你们的程序员能正确地使用unsigned,那个么就用cast来处理它的丑陋。找个实习生把它们全部换掉,之后就不用担心了。将警告作为错误打开,不会有新的警告出现。你的循环现在可能“丑陋”,但你可以理解这是你的宗教立场对有符号和无符号的结果。

        7
  •  0
  •   Lodle    7 年前

    vector.size() 返回一个 size_t var,所以只需改变 int 尺寸 应该没问题。