【问题标题】:Returning a reference of STL container element返回 STL 容器元素的引用
【发布时间】:2012-07-31 03:54:15
【问题描述】:
我在http://developer-resource.blogspot.com.au/2009/01/pros-and-cons-of-returing-references.html 看到的博客写道:
在此代码库中工作了一段时间后,我相信
返回引用是邪恶的,应该像对待
返回一个指针,这是避免它。
例如,花了一周时间调试的问题是
以下:
class Foo {
std::vector< Bar > m_vec;
public:
void insert(Bar& b) { m_vec.push_back(b); }
Bar const& getById(int id) { return m_vec[id]; }
}
此示例中的问题是客户端正在调用并获取
存储在向量中的引用。现在之后会发生什么
客户插入一堆新元素?矢量需要调整大小
内部猜测所有这些引用会发生什么?那是
就在那里无效。这导致了一个很难找到的错误
只需删除 & 即可修复。
我看不出代码有什么问题。是我误解了通过引用和 STL 容器返回,还是帖子不正确?
【问题讨论】:
标签:
c++
stl
reference
containers
【解决方案1】:
例如,您在向量中有 2 个元素:
a 和 b。您返回这些 r1 和 r2 的引用。
现在另一个客户端插入到向量中。由于向量只有两个元素存储存在。它重新分配存储。它复制a 和b 并在它们之后插入c。这将更改a 和b 的位置。所以引用 r1 和 r2 现在无效并且指向垃圾位置。
如果 getById 方法没有通过引用返回,那么将会制作一个副本,并且一切都会正常工作。
【解决方案2】:
问题更容易显示为:
std::vector<int> vec;
vec.push_back(1);
const int& ref = vec[0];
vec.push_back(ref);
vec[1] 的内容未定义。在第二个push_back 中,ref 引用了内存中vec[0] 初始化时的位置。在push_back 内部,vector 可能必须重新分配,从而使ref 所指的内容无效。
这是一个很大的不便,但幸运的是,这不是一个经常发生的问题。 Foo 是一个容器,人们插入的容器与他们刚刚通过 ID 找到的 Bar 相同吗?这对我来说似乎很有趣。每次访问都复制一份似乎是为了解决问题。如果你觉得已经够糟糕了,
void insert(const Bar& b)
{
if ((m_vec.data() <= &b) && (&b < m_vec.data() + m_vec.size()))
{
Bar copy(b);
return insert(copy);
}
else
m_vec.push_back(b);
}
在 C++11 中,最好这样写 Foo::insert(假设 Bar 有一个不错的移动构造函数):
void insert(Bar b)
{
m_vec.emplace_back(std::move(b));
}
【解决方案3】:
除了其他答案,值得指出的是,这种效果取决于容器类型。例如,对于vectors,我们有:
向量重新分配发生在成员函数必须增加
向量对象中包含的序列超出其当前存储
容量。其他插入和擦除可能会改变各种存储
序列中的地址。在所有这些情况下,迭代器或
指向序列更改部分的引用变为
无效的。如果没有发生重新分配,只有迭代器和引用
在插入/删除点之前保持有效。
而lists由于他们存储数据的方式在这方面更加放松:
当成员函数必须插入或删除时发生列表重新分配
列表的元素。在所有这些情况下,只有迭代器或引用
该点在受控序列的擦除部分变为
无效。
其他容器类型依此类推。
【解决方案4】:
来自同一篇文章的评论:
这里的问题不是通过引用返回是邪恶的,而是
您返回的引用可能会变得无效。
第 153 页,第 6.2 节“C++ 标准库:教程和
参考”——Josuttis,内容如下:
"插入或删除 elmeents 会使引用、指针和
引用以下元素的迭代器。如果插入导致
重新分配,它使所有引用、迭代器和指针无效"
您的代码示例就像持有对第一个的引用一样邪恶
向量的元素,向向量中插入 1000 个元素,以及
然后尝试使用现有的参考。