如果我从getter创建一个迭代器,程序就会中止

时间:2013-01-19 23:17:17

标签: c++ iterator getter

这是我不理解的一种奇怪行为。

我有一个带有列表的类a,以及一个getter:

class A
{
  private:
   std::list<OtherClass *> l;
  public:
   std::list<OtherClass *> getL()
   {
     return l;
   }
}

然后,如果我做了类似的事情:

A inst;
std::list<OtherClass *>::iterator itB = inst.getL().begin();
std::list<OtherClass *>::iterator itE = inst.getL().end();
for (; itB != itE; ++itB) // Instant ABORT !

但如果我这样做:

A inst;
std::list<OtherClass *> l = inst.getL();
std::list<OtherClass *>::iterator itB = l.begin();
std::list<OtherClass *>::iterator itE = l.end();
for (; itB != itE; ++itB) // It works now !

有人可以向我解释为什么会这样吗?为什么我要通过这样的临时变量不中止? 提前谢谢!

6 个答案:

答案 0 :(得分:4)

虽然所有其他答案都建议将可修改的引用作为返回值,但我会将它们设为 const

const std::list<OtherClass *> &getL() const;

此外,我将函数本身设为const,这意味着它不会修改对象本身。因此,你有一个正确的 getter方法 (它既不应修改对象,也不应返回可修改的引用)。

你可能想要引入这样一个getter函数的第二个版本,可以修改属性(如果我们不想隐藏一些需要在该属性发生变化时执行的代码,比如更新一些相关的事情):

std::list<OtherClass *> &getL();

但是,如前所述,在某些情况下,此版本不是您想要的。如果必须在setter方法中执行某些操作,则不希望公开该属性的可修改引用。调用者必须如上所示调用getter,修改值并调用setter。但是,对于大型数据结构,如列表,向量,地图等,这可能会很慢,因此您可能需要引入单个元素设置器,如:setLAt(int index, OtherClass *value);

答案 1 :(得分:2)

更改您的getter以将引用返回到基础列表。

std::list<OtherClass *> &getL()
                        ^

如果没有&符号,每次调用它时都会返回列表的副本。因此,itBitE最终成为来自不同列表的迭代器。如果这还不够糟糕的话,这两个列表是临时的,它会在for循环开始时被破坏!

如果你使用l来匹配这个,你也应该把它作为参考变量。

std::list<OtherClass *> &l = inst.getL();

答案 2 :(得分:2)

到目前为止,所有答案都告诉你如何正确地做到这一点,但我想我会告诉你更多细节为什么你的代码不起作用。正如其他人所指出的,你的“getter”按值返回列表。这是(主要)C ++特有的:程序员必须明确指定是要通过值还是通过引用传递对象。其他编程语言,例如Java(几乎)总是通过引用传递。假设你分配一个这样的变量:

MyClass a;
MyClass b = a;

在许多语言中,分配意味着:将b作为指向a的引用。然后,您就可以调用b上的方法,它的行为就像a一样。

另一方面,在C ++中,它意味着:“创建第二个对象b,然后将a的所有状态复制到b(忽略MyClass具有的可能性)复制构造函数,与此解释无关。)现在对于列表,这意味着每个元素都将被复制到新创建的列表中!(除了其他含义之外,这可能是性能问题)。

另一方面,如果您告诉编译器引用:

MyClass& b = a;

然后这个b的确表现得好像是一个。不会复制任何州,更改b会更改a

好的,现在回到您的代码示例。在第一个版本中,您有以下行:

// Creates an invalid iterator!
std::list<OtherClass *>::iterator itB = inst.getL().begin();

那里实际上是一堆不同的东西。对inst.getL()的调用将创建一个新列表,并将inst列表成员的所有内容复制到其中。然后,它将获得该副本的迭代器。之后,副本本身被破坏,迭代器将变为无效。为什么?因为您没有将列表的副本分配给任何内容。在C ++中,将破坏超出范围的堆栈分配对象(即,不使用new创建)。简单来说,只要对象不再可访问,就会发生“超出范围”:

{ // Begin scope
    MyClass o; 
    // Inside the braces, it's possible to refer to o:
    o.doSomething();
} // End scope
o.doSomething() // Will be an error, as o is not "known" anymore

如果丢弃函数的返回值,也会发生这种情况,例如写:

inst.getL(); 

这将创建列表的副本,然后再次销毁它。

现在,为什么你的第二个例子有效?因为您将列表的副本分配给临时变量,所以它们保留在范围内:

std::list<OtherClass *> l = inst.getL();

“getter”调用中的临时对象存储在l中(暂时忽略赋值运算符,RVO等),为l获取的所有迭代器现在都有效,直到{{1}超出范围。

l

所以这可行,虽然可能不如您预期的那样:迭代器在列表的副本上运行,而不是在实际数据上运行。有时可能是你想要的 - 但在你的情况下,你需要一个参考,如其他答案所示。

希望这有助于为你清理一下。

答案 3 :(得分:1)

尝试更新:

std::list<OtherClass *> getL()

std::list<OtherClass *>& getL()

请注意,inst.getL().begin();inst.getL().end();每次都会返回一份新的列表副本

答案 4 :(得分:1)

因为每次调用getL(),都会创建一个新的list,并将返回值中的信息复制到其中。

std::list<OtherClass *>::iterator itE = inst.getL().end();

还有一点需要注意的是,当你这样调用它时,你会得到一个在行尾破坏的临时list。使迭代器无效。

答案 5 :(得分:1)

getter返回列表的副本。该列表在该行的末尾死亡,迭代器无效。

您可能需要将引用返回到现有列表:

std::list<OtherClass *> & getL() { return l; }
//                     ^^^