【问题标题】:C++ use `const int` as looping variable?C ++使用`const int`作为循环变量?
【发布时间】:2011-05-12 04:00:43
【问题描述】:

我想编写有条件编译的代码,根据以下两种情况:

CASE_A:

    for(int i = 1; i <= 10; ++i){
        // do something...
    }

CASE_B:( == !CASE_A)

    {
        const int i = 0;
        // do something...
    }

也就是说,在 A 情况下,我希望对变量 i 进行正常循环,但在 B 情况下,我想将局部范围变量 i 限制为仅一种特殊情况(此处指定为 i = 0 )。显然,我可以写一些类似的东西:

    for(int i = (CASE_A ? 1 : 0); i <= (CASE_A ? 10 : 0); ++i){
         // do something
    }

但是,我不喜欢这种设计,因为它不允许我在特殊情况 B 中利用 const 声明。这样的声明可能会允许大量优化,因为这个循环的主体会受益匪浅从潜在的编译时替换 i 到它的常量值。

期待社区提供有关如何有效实现这一目标的任何提示。

谢谢!

编辑:

CASE_A 与 CASE_B 可以在编译时进行评估。

i 未作为引用传递

i 没有在正文中重新评估(否则 const 没有意义),但我不确定编译器是否会努力证明这一点

【问题讨论】:

  • 从实际的角度来看,如果解决方案适合整体语义,例如,通过#define Smart_Loop(i) ... 宏将用作:for(Smart_Loop(i)){ ... }
  • 说真的,我敢打赌,不管你如何定义它,你的小循环无论如何都会展开或优化出来。如果您不将其地址带到任何地方,每个称职的编译器都将能够删除int i。和const int i一样,只有不取地址才能删除。
  • 我推测编译器应该能够为您优化这种情况。因此,您应该编写以后最容易阅读和维护的代码。

标签: c++ conditional-compilation


【解决方案1】:

假设您没有过度简化示例,那应该没关系。假设 CASE_A 可以在编译时评估,代码:

for( int i = 0; i <= 0; ++i ) {
    do_something_with( i );
}

将生成与以下相同的机器代码:

const int i = 0;
do_something_with( i );

适用于任何体面的编译器(当然,优化已开启)。

在研究这一点时,我发现这里有一个很好的点。如果i 通过指针或引用传递给函数,编译器不能假设它没有改变。即使指针或引用是const,也是如此! (因为const可以在函数中丢弃。)

【讨论】:

  • 好吧,我想问题是:“是吗?”编译器需要证明i 在循环体内没有改变,考虑到其中代码的复杂性,这可能会也可能不会很明显。
  • 然后让你的代码更简单,其他人和编译器都可以阅读!这样它就可以准确地知道你在做什么,并且可以为你优化它。
  • 公平地说,如果 i 被声明为 const,任何会阻止编译器知道 i 将发生变化的事情都会产生错误。
  • @Daniel - 原则上同意 100%!但是,在实践中,这意味着写 if(CASE_A){...x...}else{...same x...} 而这个 x 可能是 1000 行,所以我不确定这样写是否符合“更简单”的条件
  • 问题是,为什么是x,1000行!!?如果任何代码超过几百行,它可能应该被抽象为子方法,编译器可以决定它是否值得内联(在这种情况下很可能会);因此,你有干净的代码!
【解决方案2】:

似乎是显而易见的解决方案:

template<int CASE>
void do_case();

template<>
void do_case<CASE_A>()
{
  for(int i = 1; i <= 10; ++i){
    do_something( i );
  }
}

template<>
void do_case<CASE_B>()
{
  do_something( 0 );
}

// Usage
...
do_case<CURRENT_CASE>(); // CURRENT_CASE is the compile time constant

【讨论】:

  • 我了解使用模板很好地实现编译时析取。但这会导致建立 2 个独立的 void do_something(const int i); 函数:通用函数和专用 i=0 变体?
【解决方案3】:

如果您的 CASE_B/CASE_B 确定可以表示为编译时间常数,那么您可以使用类似以下的方式以漂亮、可读的格式执行您想要的操作(这只是您使用 @987654321 的示例的变体@运算符为for循环初始化和条件):

enum {
    kLowerBound = (CASE_A ? 1 : 0),
    kUpperBound = (CASE_A ? 10 : 0)
};

for (int i = kLowerBound; i <= kUpperBound; ++i) {
    // do something
}

这清楚地表明 for 循环边界是编译时常量 - 请注意,我认为即使 ?: 表达式直接用于 for 语句的控制条款。但是,我确实认为使用枚举可以让阅读代码的人更清楚地看到它。

同样,今天任何值得一提的编译器都应该认识到i 在循环内是不变的,并且在CASE_B 的情况下还确定循环永远不会迭代。制作 i const 不会有利于编译器的优化可能性。

如果您确信如果 iconst,编译器可能能够更好地优化,那么简单的修改会有所帮助:

for (int ii = kLowerBound; ii <= kUpperBound; ++ii) {
    const int i = ii;

    // do something
}

如果i 没有被修改或者它的地址被占用(即使通过它作为参考),我怀疑这会对编译器有很大帮助(但检查它的输出 - 我可能是错的)。但是,它可能会帮助确保i 没有被不当修改或通过循环中的引用/地址传递。

另一方面,如果您在其上使用const 修饰符,您实际上可能会看到编译器产生的优化的好处——在i 的地址被采用或const 被强制转换的情况下离开,编译器仍然被允许将i 视为在其生命周期内未被修改。任何可能由抛弃const 的东西所做的修改都是未定义的行为,因此允许编译器忽略它们可能发生的情况。当然,如果您有可能执行此操作的代码,那么您的担忧比优化更大。因此,确保没有对i 的“背后”修改尝试比简单地将i 标记为const“用于优化”更重要,但使用const 可能会帮助您确定修改是否是制作(但请记住,演员表可以继续隐藏这一点)。

【讨论】:

    【解决方案4】:

    我不太确定这就是你要找的东西,但我正在使用这个宏版本的 vanilla FOR 循环,它强制循环计数器为 const 以捕获它在身体

    #define FOR(type, var, start, maxExclusive, inc) if (bool a = true) for (type var##_ = start; var##_ < maxExclusive; a=true,var##_ += inc) for (const auto var = var##_;a;a=false)
    

    用法:

    #include <stdio.h>
    
    #define FOR(type, var, start, maxExclusive, inc) if (bool a = true) for (type var##_ = start; var##_ < maxExclusive; a=true,var##_ += inc) for (const auto var = var##_;a;a=false)
    
    int main()
    {
        FOR(int, i, 0, 10, 1) {
            printf("i: %d\n", i);
        }
    
        // does the same as: 
        for (int i = 0; i < 10; i++) {
            printf("i: %d\n", i);
        }
    
        // FOR catches some bugs:
        for (int i = 0; i < 10; i++) {
            i += 10; // is legal but bad
            printf("i: %d\n", i);
        }
    
        FOR(int, i, 0, 10, 1) {
            i += 10; // is illlegal and will not compile
            printf("i: %d\n", i);
        }
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2019-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-14
      • 1970-01-01
      • 2022-12-11
      相关资源
      最近更新 更多