【问题标题】:Why are vector contents changed after the method scope finishes?为什么方法范围结束后向量内容会更改?
【发布时间】:2016-12-22 10:12:35
【问题描述】:

以下虚拟程序模仿了我正在排除故障的另一个程序的行为。

#include <iostream>
#include <vector>

class A
{
public:
    std::vector<int> data;

    void DoTheThing()
    {
        while (data.size() < 10) {
            data.push_back(1);
        }
    }
};

class B
{
public:
    std::vector<A> objs;

    B()
    {
        A one, two, three;
        objs.push_back(one);
        objs.push_back(two);
        objs.push_back(three);
    }

    void DoTheThing()
    {
        for (auto obj: objs) {
            obj.DoTheThing();
            std::cout << "DEBUG length during=" << obj.data.size() << std::endl;
        }
    }
};

int main()
{
    B b;
    b.DoTheThing();
    for (auto obj : b.objs) {
        std::cout << "DEBUG length  after=" << obj.data.size() << std::endl;
    }
}

我编译并运行为:

$ g++ -Wall --std=c++11 -o test test.cpp
$ ./test
DEBUG length during=10
DEBUG length during=10
DEBUG length during=10
DEBUG length  after=0
DEBUG length  after=0
DEBUG length  after=0
$

由于某种原因,bobjs 向量中的A 对象的状态在b.DoTheThing() 调用和随后的打印语句之间发生变化。我的问题是发生了什么A 对象 data 向量是否以某种方式超出范围并被删除,或者可能是整个 A 对象?这似乎是一个范围界定问题——甚至可能是一个微不足道的简单问题——但自从我用 C++ 编程以来,它已经足够长了,我不确定。在其他方法中调用b.DoTheThing() 后,如何使data 向量的内容保持不变?

【问题讨论】:

    标签: c++ for-loop vector scope


    【解决方案1】:

    “由于某种原因,b 的 objs vector 中的 A 对象的状态在 b.DoTheThing() 调用和随后的打印语句之间发生了变化。我的问题是发生了什么?"

    void DoTheThing()
    {
      for(auto obj : objs)
       // ^^^^^^^ receive by value due to implicit copy
    

    您实际上是将objs 的单独副本复制到单独的临时objs 中。在B::DoThething() 完成后,这些临时对象将被销毁。为避免复制,请使用reference

      for(auto& obj : objs)
       // ^^^^^^^ receive by reference
    

    main() 中的类似循环也是如此。


    真正的问题可能是:“如何避免此类事故?”

    如果你负担得起,那么将复制构造函数设为explicit 以避免隐式复制

    class A {
    public:
      A () = default;      
      explicit A(const A&) = default;  // implicit copy is avoided
      A (A&&) = default;  // to avoid RVO related issues
      ...
    };
    

    这是一个demo,它显示了explicit 如何生成编译错误以捕获for 循环中意外复制的问题。


    explicit 有其自身的语法限制。有的可以解决,有的不能。请参阅以下问题了解 1 个此类问题(以及如何解决):

    What is the effect of 'explicit' keyword on the Return Value Optimization (RVO)?

    【讨论】:

    • 制作复制构造函数explicit 可能会导致其他问题。最好只学习语言以了解复制的位置,并在需要时避免复制。
    • @JonathanWakely,其中一个已知问题是 RVO 问题。这可以在某些情况下解决,我已在答案中进行了更新。是的,explicit 有其自身的局限性,但我觉得它们值得。几周前我就遇到过这种情况,调试起来真的很困难。我们也可以有一个#ifdef/#endifs 并在它下面创建一个#define conditional_explicit explicit。这对于最终检查任何此类副本并恢复正常很有用。
    【解决方案2】:

    问题出在for循环的范围内:

    void DoTheThing()
        {
            for (auto obj: objs) {
                obj.DoTheThing();
                std::cout << "DEBUG length during=" << obj.data.size() << std::endl;
            }
        }
    

    您正在复制 A 对象并在副本中进行修改,而不是在原始对象中进行修改。

    改成:

    for (auto& obj: objs) {
      ...
    }
    

    它应该按预期工作。

    【讨论】:

      【解决方案3】:

      您必须按如下方式更改您的 for 循环:

      for (auto& obj : b.objs)
      

      通过这种方式,您可以通过引用来处理容器中的实际对象。 如果您将其声明为:

      for (auto obj : b.objs)
      

      obj 将是一个副本

      【讨论】:

      • 太棒了。我知道这很简单。谢谢!
      • 您可以通过for_each 以及下一步玩得开心:-)
      【解决方案4】:

      您应该使用按引用接收,而不是按值接收。

      for (auto obj : b.objs) {
              std::cout << "DEBUG length  after=" << obj.data.size() << std::endl;
          }
      

      上面这段代码是按值接收的,改成

      for (auto& obj : b.objs) {
              ........
          }
      

      【讨论】:

        猜你喜欢
        • 2013-09-07
        • 1970-01-01
        • 2011-05-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-19
        • 2015-08-08
        相关资源
        最近更新 更多