【问题标题】:C++ returing a reference to a value in vectorC++ 返回对向量中值的引用
【发布时间】:2018-10-24 00:19:16
【问题描述】:

给定以下程序:

#include <iostream>
#include <utility>
#include <vector>
#include <unordered_map>

#ifdef WITHPAIR
auto get_state() {
    std::pair<std::vector<unsigned>, std::unordered_map<unsigned, unsigned&>> st;

    auto& v = st.first;
    auto& index = st.second;

    v.assign({1u,2u,3u,4u});

    index.insert({0u, v[0]});
    index.insert({1u, v[1]});
    index.insert({2u, v[2]});
    index.insert({3u, v[3]});

    return st;
}

#else

std::pair<std::vector<unsigned>, std::unordered_map<unsigned, unsigned&>> get_state() {
    std::vector v{1u,2u,3u,4u};
    std::unordered_map<unsigned, unsigned&> index{
            {0u, v[0]},
            {1u, v[1]},
            {2u, v[2]},
            {3u, v[3]}
    };

    return {v, index};
}
#endif

auto main() -> int {
    auto [v, index] = get_state();
//    auto [v, index] = std::move(get_state());
    std::cout << v[0] << " " << index.at(0) << std::endl;
    v[0] = 5;
    std::cout << v[0] << " " << index.at(0) << std::endl;
    std::cout << v[1] << " " << index.at(1) << std::endl;
    v[2] = 17;
    std::cout << v[2] << " " << index.at(2) << std::endl;
    std::cout << v[3] << " " << index.at(3) << std::endl; 

    return 0;
}

http://coliru.stacked-crooked.com/a/f9e528074ae78c03

不使用-DWITHPAIR 进行编译以查看第二个函数的行为


函数get_state有两个版本。

只有第一个函数似乎具有正确的行为,并且在从函数返回时实际上使该数据可用(从链接的程序中可以看到);第二个函数的行为不是这样,并且 unordered_map 中的值与向量中的值不同。

我的问题是两个:

  • 第一个函数的行为是否正确?是否是未定义的行为?
  • 为什么第二个会改变向量?

【问题讨论】:

  • 因为第二个有UB,第一个没有
  • @Slava 请将此作为答案。第二个的UB是多少?
  • 第二个v 没有被移动,而是被复制,所以所有的引用都失效了。如果您明确移动v,它应该会修复它。
  • 因为RVO导致第一个没有副本
  • 是的,它被复制到,你也应该移动它,但是为了效率和一致性,但不移动 v 会由于地图中的无效引用而带来 UB。

标签: c++ reference c++17 stdvector unordered-map


【解决方案1】:

由于NRVO,您的第一个功能似乎可以工作:

在return语句中,当操作数是具有自动存储持续时间的非易失性对象的名称时,该对象不是函数参数或catch子句参数,并且属于同一类类型(忽略cv-资格)作为函数返回类型。这种复制省略的变体被称为 NRVO,“命名返回值优化”。

如果发生这种情况,则没有执行复制,并且地图中的引用仍然有效,但由于 NRVO 是一种优化,因此不能保证您只是幸运地工作。

在第二个函数中,您创建std::pair&lt;std::vector&lt;unsigned&gt;, std::unordered_map&lt;unsigned, unsigned&amp;&gt;&gt; 类型的临时对象,并使用左值vindex 对其进行初始化,因此为它们调用了复制构造函数,并且所有引用在原始v 时都无效被摧毁。修复可能是显式调用std::move

return {std::move(v), index};

并且作为标准保证引用保持有效并指向新向量中的元素,因此应该不再有 UB。 index 也应该移动以提高效率和一致性,但我留下了它,因为它与问题无关。

这是一种非常危险的编程方式,我会考虑不同的数据类型(可能是向量中的索引而不是引用)。

【讨论】:

  • 最后感谢您的建议。在尝试复制这对配对后,我很快意识到了这一点
  • @smac89 np,在容器中引用通常不是一个好主意,但对矢量元素的引用更糟糕。
  • 这是 NRVO,不能保证。
猜你喜欢
  • 1970-01-01
  • 2023-04-11
  • 2014-03-13
  • 1970-01-01
  • 2023-01-04
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
相关资源
最近更新 更多