如果我从getter中创建迭代器,程序就会中止
Program abort if I make an iterator from a 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 !
谁能给我解释一下为什么会这样?为什么我要通过这样一个临时变量来不中止呢?
虽然所有其他答案都建议可修改的引用作为返回值,但我将它们设置为const:
const std::list<OtherClass *> &getL() const;
我还将函数本身设置为const,这意味着它不会修改对象本身。这样你就有了一个正确的getter方法(它既不应该修改对象,也不应该返回一个可修改的引用)。
你可能想要引入这样一个getter函数的第二个版本,让可以修改属性(如果我们不想隐藏一些在属性改变时需要执行的代码,比如更新一些相关的东西):
std::list<OtherClass *> &getL();
然而,正如已经指出的,在某些情况下,这个版本并不是您想要的。如果必须在setter方法中执行某些操作,则不希望公开该属性的可修改引用。调用者必须像上面看到的那样调用getter,修改值并调用setter。然而,对于大数据结构,如列表,向量,映射等,这可能是缓慢的,所以你可能想要引入单元素设置:setLAt(int index, OtherClass *value);
到目前为止,所有的答案都告诉你如何正确地做到这一点,但我想我要给你一些更多的细节,为什么你的代码不能工作。正如其他人指出的那样,你的"getter"是按值返回列表。这是(主要是)c++特有的:作为程序员,你必须明确地指定是要通过值传递对象还是通过引用传递对象。其他编程语言,例如Java,将(几乎)总是通过引用传递。假设你像这样赋值一个变量:
MyClass a;
MyClass b = a;
在许多语言中,赋值意味着:使b
成为指向a
的引用。然后,您将能够调用b
上的方法,并且它的行为就像a
一样。
意味着:"创建第二个对象b
,然后将a
的所有状态复制到b
(忽略MyClass有复制构造函数的可能性,这与本解释无关)。现在对于列表,这意味着每个元素都将被复制到新创建的列表中!(这可能是一个性能问题,除了其他影响)。
另一方面,如果你告诉编译器要引用a:
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
获得的所有迭代器现在都将有效,直到l
超出作用域。
std::list<OtherClass *>::iterator itB = l.begin(); // valid
所以这可以工作,尽管可能不像您期望的那样:迭代器操作的是列表的副本,而不是实际数据。这有时可能是你想要的——但在你的情况下,你想要一个参考其他答案。
希望这对你有帮助。
将getter更改为返回引用到底层列表
std::list<OtherClass *> &getL()
^
如果没有&符号,每次调用它时,它都会返回列表的副本。因此,itB
和itE
最终是来自不同列表的迭代器。如果这还不够糟糕,那两个列表是临时的,在for
循环开始时将被销毁!
如果你使用l
,为了匹配这个,你也应该把它变成一个引用变量。
std::list<OtherClass *> &l = inst.getL();
getter返回列表副本。列表在行尾终止,迭代器无效。
您的意思可能是返回一个引用到现有列表:
std::list<OtherClass *> & getL() { return l; }
// ^^^
因为每次调用getL()
时,都会创建一个新的list
,并将返回值中的信息复制到其中。
std::list<OtherClass *>::iterator itE = inst.getL().end();
还需要注意的是,当你这样调用它时,你最终会得到一个临时的list
,它会在行尾销毁。使迭代器无效。
尝试更新:
std::list<OtherClass *> getL()
std::list<OtherClass *>& getL()
注意inst.getL().begin();
和inst.getL().end();
每次都返回一个新的list副本
- 如何检查第三个 API 是否在 Linux 中为 c/c++ 程序创建了一个新线程?
- 从头开始为应用程序创建 docker 映像是否有意义?
- 如何查看在程序级别为我的程序创建了多少 vtable 和 vpointer
- 如何在运行时对程序创建的.sys文件设置文件版本
- 从函数应用程序创建 std::vector
- 将 Qml/C++ 应用程序创建为插件
- 如何为带有计时功能的程序创建计时器?
- 使用Visual C ,卡在程序创建分配中
- 从Ubuntu中的CPP程序创建新文件
- 通过编译应用程序创建Windows服务
- 我们可以使用mfc应用程序创建pdf文件吗
- 使用可执行应用程序创建和"Scheduling" pthreads
- 通过 JNI 从应用程序创建 JVM 后C++找不到类
- 如何为多线程应用程序创建全局对象
- 为C++ Linux 应用程序创建隐藏的配置文件
- C 程序创建SIN COS和切线值
- MongoDB使用C 驱动程序创建稀疏索引
- 如何以编程方式为应用程序创建文件夹,以使用 BOX REST API 在其中上传内容
- 有没有办法从 C 程序创建数据收集器集
- 从C++应用程序创建Mac OS X的安装程序