【问题标题】:temporary object in range-based for基于范围的临时对象
【发布时间】:2012-04-26 13:47:44
【问题描述】:

我知道一般来说,基于范围的for 循环中的临时生命周期会扩展到整个循环(我已阅读C++11: The range-based for statement: "range-init" lifetime?)。因此,做这样的事情通常是可以的:

for (auto &thingy : func_that_returns_eg_a_vector())
  std::cout << thingy;

现在,当我尝试做一些我认为与 Qt 的 QList 容器相似的事情时,我遇到了内存问题:

#include <iostream>
#include <QList>

int main() {
  for (auto i : QList<int>{} << 1 << 2 << 3)
    std::cout << i << std::endl;
  return 0;
}

这里的问题是 valgrind 在 QList 类中的某处显示无效的内存访问。但是,修改示例以将列表存储在变量中会提供正确的结果:

#include <iostream>
#include <QList>

int main() {
  auto things = QList<int>{} << 1 << 2 << 3;
  for (auto i : things)
    std::cout << i << std::endl;
  return 0;
}

现在我的问题是:在第一种情况下我是否在做一些愚蠢的事情,导致例如未定义的行为(我没有足够的经验阅读 C++ 标准来为自己回答这个问题)?或者这是我如何使用QListQList 是如何实现的问题?

【问题讨论】:

    标签: c++ qt c++11


    【解决方案1】:

    由于您使用的是 C++11,you could use initialization list instead。这将通过 valgrind:

    int main() {
      for (auto i : QList<int>{1, 2, 3})
        std::cout << i << std::endl;
      return 0;
    }
    

    该问题与基于范围的甚至 C++11 并不完全相关。下面的代码演示了同样的问题:

    QList<int>& things = QList<int>() << 1;
    things.end();
    

    或:

    #include <iostream>
    
    struct S {
        int* x;
    
        S() { x = NULL; }
        ~S() { delete x; }
    
        S& foo(int y) {
            x = new int(y);
            return *this;
        }
    };
    
    int main() {
        S& things = S().foo(2);
        std::cout << *things.x << std::endl;
        return 0;
    }
    

    无效读取是因为来自表达式S()(或QList&lt;int&gt;{})的临时对象在声明后被破坏(遵循C++03和C++11 §12.2/5),因为编译器没有想法foo()(或operator&lt;&lt;)方法将返回该临时对象。所以你现在指的是释放内存的内容。

    【讨论】:

    • 感谢您的澄清。愚蠢的我,当然我应该首先使用初始化列表——我不知何故根本没有考虑过。可能是由于 Qt 的示例总是在类似情况下使用 &lt;&lt;
    • 嗯,看来 C++11 支持仅在 Qt 4.8 及更高版本中可用。但对于这种情况,我可以轻松地使用标准库中的容器。
    • 可以通过转换为QList&lt;int&gt; const&amp;(即写for (auto i : static_cast&lt;QList&lt;int&gt; const&amp;&gt;(QList&lt;int&gt;{} &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3)))来规避这个问题吗?这样,如果我正确阅读了第 6.5.4 节,它将绑定到 for 循环初始化中的 const 引用,而这反过来又会将临时变量的生命周期延长到循环范围。
    • 我无法评论有效性,但 valgrind 说 no, you cannot :)
    • 这里的真正问题是QListoperator&lt;&lt;返回类型(非const左值引用)。 for (auto i : QList&lt;int&gt;{}) 不使用 &lt;&lt; 很好,因为 QList 实例是一个右值,并且可以很好地与 auto&amp;&amp; 的语义配合使用。
    【解决方案2】:

    编译器不可能知道作为对operator &lt;&lt; 的三次调用结果的引用绑定到临时对象QList&lt;int&gt;{},因此临时对象的生命周期不会延长。编译器不知道(也不能期望知道)函数的返回值,除了它的类型。如果它是一个引用,它不知道它可能绑定到什么。我很确定,为了应用延长生命的规则,绑定必须是直接的。

    这应该有效,因为列表不再是临时的:

    #include <iostream>
    #include <QList>
    
    int main() {
      auto things = QList<int>{};
      for (auto i : things << 1 << 2 << 3)
        std::cout << i << std::endl;
      return 0;
    }
    

    这应该可行,因为绑定是直接的,因此可以应用规则:

    #include <iostream>
    #include <QList>
    
    int main() {
      for (auto i : QList<int>{1, 2, 3})
        std::cout << i << std::endl;
      return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2022-06-26
      • 1970-01-01
      • 1970-01-01
      • 2018-12-28
      • 2017-11-04
      • 1970-01-01
      • 1970-01-01
      • 2015-03-03
      • 1970-01-01
      相关资源
      最近更新 更多