移动构造函数行为

Move constructor behaviour

我最近偶然发现了移动构造函数的一些奇怪行为(从我的角度来看很奇怪)。使用 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:

Inserts a new element into the container constructed in-place with the given args if there is no element with the key in the container.

Careful use of emplace allows the new element to be constructed while avoiding unnecessary copy or move operations.

使用默认移动构造函数,存储在map元素中的迭代器和我的引用相同:

Test(Test &&) = default; 

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

Test(Test &&) {} 

GCC still returns true 但是 VS returns false

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

这一行:

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

...新插入的Test对象是从std::forward<Test>(Test{ it })构造的,所以确实调用了移动构造函数(因为转发,复制省略在这里不起作用)。如果想直接构造Test,可以用m.emplace(0, it)代替

现在我们可以看到

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

  • Test(Test &&) {}一起,m.at(0).v被值初始化,it随着Test{ it }.v指定的临时对象被销毁而失效。该程序导致未定义的行为。

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