【问题标题】:Is it possible to ban modifying loop variables inside the body of for-loops?是否可以禁止在 for 循环体内修改循环变量?
【发布时间】:2020-03-01 23:13:30
【问题描述】:

在 C 或 C++ 中,在 for 循环中修改循环变量是导致严重错误的根源:

int main() {
    std::vector<int> v (30);
    std::iota(v.begin(), v.end(), 0);
    int j = 0;
    for (size_t i = 0; i < v.size(); i++) {
        std::cout << v[i] << ' ' << i << '\n';
        i++; // oops, I mean j++
    }
    std::cout << j << '\n';
}

有什么方法可以禁止或警告在编译器或其他东西的帮助下修改循环体内的循环变量?如果可以,我该怎么做?

【问题讨论】:

  • 我会建议更好的命名。 ij 不是很具体,这就是为什么您最终在示例中修改 i 而不是 j
  • 在其他一些语言中,这可以通过一种特殊类型的迭代器来完成,它返回元素及其索引的元组,但我认为在 C 中这样做是一种反模式。也许在 C++ 中可行。
  • @TedLyngmo:这个问题明确询问了 C 和 C++。示例代码是 C++ 并不能否定这一点。发帖者在真正只处理一个时,通常会同时标记 C 和 C++,但这个问题对两者都进行提问并非没有道理。
  • 重新。返回元素及其索引的迭代器,请参阅:stackoverflow.com/questions/24881799/…(特定于 C++)

标签: c++ c


【解决方案1】:

编辑:回答您的最新评论:

是的,我正在寻找的只是是否有一个编译器选项会发出警告。如果没有,也许我应该更仔细地编码。

不,不幸的是,在 C 中没有。是的,您应该更仔细地编码。一般来说,我建议您考虑为什么您想要拥有这样的功能。如果在循环中“保护”索引变量是一个问题,我会首先问自己我的编码风格是否有意义并且是一致的。


正如Eric Postpischil 所注意到的,您可以使用一个临时变量将您的索引变量隐藏在一个内部块中,如const。这样,如果您尝试修改它,编译器就会出错。

然而,这会产生阴影警告(尤其是 -Wshadow 标志,这很常见):

shadow.c:9:14: warning: declaration of ‘i’ shadows a previous local [-Wshadow]
    const int i = t;
              ^
shadow.c:4:11: note: shadowed declaration is here
  for (int i = 0; i < 10; ++i)

为避免这种情况,您可以使用简单的诊断 #pragma 指令并暂时禁用该特定代码块的警告:

for (int i = 0; i < 10; ++i)
{
    const int t = i;
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wshadow"
    {
        const int i = t;
        printf("i = %d\n", i);
        i = 4; // Will yield a compiler error.
    }
    #pragma GCC diagnostic pop
}

上面的代码有效(当然删除了i = 4)并且在GCC和Clang中使用-Wall -Werror -Wshadow -pedantic编译没有警告。

注意:这肯定不是好的做法,但 AFAICT 它是在 C 中实现这种行为的唯一方法。

【讨论】:

    【解决方案2】:

    在 C 中,你可以隐藏名称并重新声明另一个与 const 同名的标识符,但你需要使用一些中间对象来帮助,这并不漂亮:

    for (int i = 0; i < 10; ++i)
    {
        const int t = i, i = t;
        printf("i = %d.\n", i);  // Works.
        i = 4;                   // Yields compiler error.
    }
    

    我不建议这样做,但您可以通过以下方式使其不那么难看:

    #define Protect(Type, Original) \
        const Type Auxiliary_##Original = Original, Original = Auxiliary_##Original
    

    然后使用:

    for (int i = 0; i < 10; ++i)
    {
        Protect(int, i);
        printf("i = %d.\n", i);  // Works.
        i = 4;                   // Yields compiler error.
    }
    

    【讨论】:

    • 你可以在 C++ 中做同样的事情,但是这种方法也会触发令人讨厌的阴影警告。
    • 对不起,我可能误解了,但是它是如何回答这个问题的?
    • @GuillaumePetitjean:定义const int i = t;后,由于不可见,无法修改for语句中定义的i,从而满足题主“禁止”修改的要求循环体内的循环变量。此外,循环变量i 无需修改即可使用,因为内部i 中提供了一个副本,并且尝试修改它(通过正常方式;可能会获取其地址并做坏事)将导致编译器诊断。
    • 这并没有真正解决保护循环变量在循环中不被改变的问题。范围内的新变量 I 不是循环变量,您只是在引入更多复杂性以尝试解决 C 和 C++ 的特性。我们都知道,增加复杂性和解决问题很少能产生好的结果,并且是最常见的代码腐烂来源之一。
    • @RichardChambers:它如何不保护循环变量在循环中不被更改?在循环中,将此答案中显示的设置代码算作其中的一部分,因此“内部”是从第二个 i 的声明到右大括号的部分,不可能更改循环变量。因此它受到保护。
    【解决方案3】:

    对于 C++,您可以创建一个索引类来使用。因此,以下内容将是一个起点。我确信它可以改进,因为我没有考虑太多。

    class CIndex {
    private:
        size_t  m_index;
    public:
        CIndex(size_t i=0) : m_index(i) {}
        ~CIndex() {};
    
        size_t inc(void) { return ++m_index; }
        size_t val(void) { return m_index; }
    
        bool operator < (size_t i) { return m_index < i; }
        CIndex & operator =(size_t i) = delete;
    };
    

    它会像这样使用:

    for (CIndex x; x < 10; x.inc()) {
        std::cout << argv[x.val()];
        x = 3;    // generates an error with Visual Studio 2017
    }
    

    您可以使用转换运算符修改上述类,使其更直观并类似于标准的size_t 变量。还要添加一个减量运算符。由于这个想法是使用它代替size_t,因此您不再需要比较运算符,因为编译器将进行转换并使用内置比较来结束循环。您可能还希望能够指定可选的增量或减量。

    修改后的类如下所示:

    class CIndex {
    private:
        size_t  m_index;
    public:
        CIndex(size_t i = 0) : m_index(i) {}
        ~CIndex() {};
    
        size_t inc(size_t i = 1) { return (m_index += i); }    // increment operator
        size_t dec(size_t i = 1) { return (m_index -= i); }    // decrement operator
    
        CIndex & operator =(size_t i) = delete;
        operator size_t() const { return m_index; }
    };
    

    这将允许您在几乎可以使用size_t 的任何地方使用CIndex。所以数组索引可以写成std::cout &lt;&lt; argv[x];而不是std::cout &lt;&lt; argv[x.val()];

    但是对于 C 语言规范,这并没有允许您将变量标记为在特定范围内不可变或不变。

    您真正需要的是能够标记特定的代码行以允许更改变量并标记不允许更改变量的其他代码行。 C 语言规范没有这个特性。

    【讨论】:

    • 循环体内部的x.inc() 怎么样?
    • 这看起来不错。仍然有人可以在循环体内调用 x.inc() ,但大多数调用将是故意的 troll 目的,这似乎足以为错误修改提供障碍。
    • @frozenca 你低估了copypasta的力量。
    • 无中生有。我的意思是x 仍然可以通过x.inc() 修改。一个错误的复制粘贴就足以搞砸了。
    【解决方案4】:

    如果您使用 C++ ranged-for,您可以将循环变量设为const。例如

    for (const size_t i : boost::irange<size_t>(0, v.size()))
    {
        std::cout << v[i] << ' ' << i << '\n';
        // i++; // error, can't modify const
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-21
      • 1970-01-01
      • 2019-02-02
      • 2017-06-21
      相关资源
      最近更新 更多