移动构造函数的行为

时间:2018-12-15 15:14:36

标签: c++ move-constructor

我最近偶然发现了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;
}

因此,我在创建元素时将迭代器存储到map元素中map的末尾。我也参考它以供以后比较。来自std::unordered_map::emplace

  

将新元素插入到容器中,该容器使用   如果容器中没有带键的元素,则给出args。

     

谨慎使用emplace可以在构建新元素的同时   避免不必要的复制或移动操作。

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

Test(Test &&) = default; 

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

Test(Test &&) {} 

GCC still returns true,但VS返回false

以防万一尝试使用c ++ 17,结果相同。所以有人可以解释一下这里发生了什么吗?

1 个答案:

答案 0 :(得分:4)

在这一行:

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

...新插入的Test对象是根据std::forward<Test>(Test{ it })构造的,因此确实调用了move构造函数(由于转发,复制省略在这里不起作用)。如果您想直接构造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的行为是合理的。