【问题标题】:Move constructor behaviour移动构造函数行为
【发布时间】:2018-12-15 15:14:35
【问题描述】:

我最近从 move 构造函数中偶然发现了一些奇怪的行为(在我看来很奇怪)。使用 GCC 和 Visual Studio 编译时结果不同。我想听听这种行为的解释,不要认为这是一个错误,但可能是特定于编译器的。

考虑以下代码:

#include <iostream>
#include <unordered_map>

struct Test
{
    std::unordered_map<int, int> v;
    std::unordered_map<int, int>::iterator vend;

    Test(std::unordered_map<int, int>::iterator &it)
        : vend { v.end() }
    {
        it = this->vend;
    };

    Test() = delete;
    Test(Test const &) = delete;
    Test(Test &&) = default; // <- line in question
};

int main()
{
    std::unordered_map<int, int>::iterator it;
    std::unordered_map<int, Test> m;
    m.emplace(0, Test{ it });
    std::cout << std::boolalpha << (m.at(0).v.end() == it) << "\n";

    return 0;
}

所以我在创建元素时将迭代器存储到地图元素中的地图末尾。我也参考了它,以便稍后进行比较。来自std::unordered_map::emplace

将新元素插入就地构造的容器中 如果容器中没有带有键的元素,则给定 args。

小心使用 emplace 允许在构造新元素的同时 避免不必要的复制或移动操作。

使用默认的move构造函数,map元素中存储的迭代器和我的引用是一样的:

Test(Test &&) = default; 

Results 在 GCC 中是 true,在 VS 中是 true。现在,如果我将移动构造函数更改为:

Test(Test &&) {} 

GCC still returnstrue 但VS返回false

以防万一尝试使用 c++17,结果相同。那么谁能解释一下这里发生了什么?

【问题讨论】:

  • Test{it} 作为外参数集it。 emplace 做了一个动作,这将使迭代器无效。将m.at(0).v.end() 与无效的it 进行比较。谁知道会发生什么?许个愿。
  • @Eljay 地图内的迭代器将被保留,因为他返回的迭代器是在 emplace 内移动的地图......如果我错了,请纠正我
  • @Kilzone,在这种情况下,您认为哪个构造函数会用于“就地构造”?
  • @Eljay - 关联容器迭代器不会在移动时失效,这也包括结束迭代器。
  • 回到 Killzone Kid 的问题,我不知道 std::unordered_map&lt;int, int&gt;::iterator 根据 C++ 标准对默认构造做了什么。似乎一个平台将其初始化为与end() 相同(或者您很幸运),而另一个平台使其处于未初始化状态。如果您输入: vend{std::move(rhs.vend)},您可能会得到更一致的结果。

标签: c++ move-constructor


【解决方案1】:

在这一行:

m.emplace(0, Test{ it });

...新插入的Test对象是从std::forward&lt;Test&gt;(Test{ it })构造的,所以确实调用了move构造函数(因为转发,这里复制省略不起作用)。如果要直接构造Test,可以改用m.emplace(0, it)

现在我们可以看到

  • 使用Test(Test &amp;&amp;) = default;,将Test{ it }.v 指定的临时对象移动到m.at(0).v。如果it 仍然有效(this is not guaranteed),m.at(0).v.end() == it 的计算结果为true;否则程序会导致未定义的行为。

  • 使用Test(Test &amp;&amp;) {}m.at(0).v 是值初始化的,it 无效,因为Test{ it }.v 指定的临时对象被销毁。该程序导致未定义的行为。

在libstdc++的实现中,不同unordered_maps的结束迭代器具有相同的值(类似于std::istream_iterator),所以GCC的行为是合理的。

【讨论】:

  • 这是一个很好的解释,m.emplace(0, it) 工作得很好,但是如果 Test 构造函数中的参数多于一个,如何避免临时的呢? m.emplace(0, it, somethingelse)?
  • @KillzoneKid 你可以试试m.emplace(std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(it, somethingelse))
  • 太棒了,正是我需要的!
猜你喜欢
  • 1970-01-01
  • 2011-05-22
  • 1970-01-01
  • 2021-09-15
  • 1970-01-01
  • 2017-11-03
  • 1970-01-01
  • 2014-11-08
相关资源
最近更新 更多