这是我不理解的一种奇怪行为。
我有一个带有列表的类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 !
有人可以向我解释为什么会这样吗?为什么我要通过这样的临时变量不中止? 提前谢谢!
答案 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()
^
如果没有&符号,每次调用它时都会返回列表的副本。因此,itB
和itE
最终成为来自不同列表的迭代器。如果这还不够糟糕的话,这两个列表是临时的,它会在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; }
// ^^^