2

我在http://developer-resource.blogspot.com.au/2009/01/pros-and-cons-of-reuring-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 容器返回,或者帖子不正确?

4

4 回答 4

4

问题更容易显示为:

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_backvector可能必须重新分配,从而使ref所指的内容无效。


这是一个很大的不便,但幸运的是,这不是一个经常发生的问题。Foo人们插入的容器是否与他们Bar刚刚通过 ID 找到的容器相同?这对我来说似乎很有趣。每次访问都复制一份似乎是为了解决问题。如果你觉得已经够糟糕了,

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));
}
于 2012-07-31T05:04:13.403 回答
4

例如,您在向量中有 2 个元素:

ab。您返回这些r1和的引用r2

现在另一个客户端插入到向量中。由于向量只有两个元素存储存在。它重新分配存储。它复制a并在它们之后b插入c。这会改变 和 的a位置b。所以引用r1r2现在无效并且指向垃圾位置。

如果 getById 方法没有通过引用返回,则会制作一个副本,并且一切都会正常工作。

于 2012-07-31T04:30:16.463 回答
2

除了其他答案,值得指出的是,这种效果取决于容器类型。例如,对于向量,我们有:

当成员函数必须将向量对象中包含的序列增加到超出其当前存储容量时,就会发生向量重新分配。其他插入和擦除可能会改变序列内的各种存储地址。在所有这些情况下,指向序列更改部分的迭代器或引用都将变为无效。如果没有发生重新分配,则只有插入/删除点之前的迭代器和引用保持有效。

列表由于其存储数据的方式在这方面更为宽松:

当成员函数必须插入或删除列表的元素时,就会发生列表重新分配。在所有这些情况下,只有指向受控序列的已擦除部分的迭代器或引用变得无效。

其他容器类型依此类推。

于 2012-07-31T06:17:50.947 回答
1

来自同一篇文章的评论:

这里的问题不是通过引用返回是邪恶的,只是你返回的引用可能变得无效。

第 153 页,“C++ 标准库:教程和参考”的第 6.2 节 - Josuttis,内容如下:

“插入或删除 elmeents 会使引用以下元素的引用、指针和迭代器无效。如果插入导致重新分配,它会使所有引用、迭代器和指针无效”

您的代码示例与持有对向量的第一个元素的引用一样邪恶,将 1000 个元素插入向量中,然后尝试使用现有的引用。

于 2018-01-17T10:22:47.640 回答