0

这是我不明白的一种奇怪行为。

我有一个带有列表的 a 类,上面有一个吸气剂:

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 !

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

4

6 回答 6

5

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

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

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

您可能想要引入可以修改属性的此类 getter 函数的第二个版本(如果我们不想隐藏一些在该属性更改时需要执行的代码,例如更新一些相关的东西):

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

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

于 2013-01-19T23:21:55.863 回答
3

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

MyClass a;
MyClass b = a;

在许多语言中,赋值意味着:b引用指向a. 然后,您将能够调用 上的方法b,并且它的行为就像它一样a

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

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

MyClass& b = a;

那么这b确实会表现得好像它是一个。不会复制任何状态,改变b会改变a

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

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

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

{ // 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现在都将有效,直到l超出范围。

std::list<OtherClass *>::iterator itB = l.begin(); // valid

所以这行得通,尽管可能不像您预期​​的那样:迭代器在您的列表副本上运行,而不是在实际数据上运行。有时这可能是您想要的 - 但在您的情况下,您需要其他答案所建议的参考。

希望这有助于为您澄清一点。

于 2013-01-19T23:46:10.740 回答
2

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

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

如果没有与号,它会在您每次调用它时返回列表的副本。因此,itB最终itE成为来自不同列表的迭代器。如果这还不够糟糕,那么这两个列表是临时的,在for循环开始时已经被销毁了!

如果你使用它来匹配这个,你l也应该让它成为一个引用变量。

std::list<OtherClass *> &l = inst.getL();
于 2013-01-19T23:19:11.567 回答
1

尝试更新:

std::list<OtherClass *> getL()

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

记下您的inst.getL().begin();inst.getL().end();每次返回一份新的列表副本

于 2013-01-19T23:19:27.773 回答
1

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

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

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

于 2013-01-19T23:19:30.117 回答
1

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

您可能的意思是返回对现有列表的引用:

std::list<OtherClass *> & getL() { return l; }
//                     ^^^
于 2013-01-19T23:19:54.573 回答