从非常量到常量对模板参数的隐式转换以及在 C++ 中的 unoredered_map::insert 上调用复制/移动向量

问题描述 投票:0回答:1

代码

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

using namespace std;

struct Foo {
    Foo(const int value) : val(value) {
        cout << "Foo(int), val: " << val << '\n';
    }
    Foo(Foo & foo) {
        val = foo.val;
        cout << "Foo(Foo &)" << '\n';
    }
    Foo(const Foo & foo) {
        val = foo.val;
        cout << "Foo(const Foo &)" << '\n';
    }
    Foo(Foo && foo) {
        val = foo.val;
        cout << "Foo(Foo &&)" << '\n';
    }
    ~Foo() { cout << "~Foo(), val: " << val << '\n'; }
    Foo& operator=(const Foo& rhs)
    {
        cout << "Foo& operator=(const Foo& rhs), rhs.val: " << rhs.val;
        val = rhs.val;
        return *this;
    }
    bool operator==(const Foo& rhs) const { return val == rhs.val; }
    bool operator<(const Foo& rhs)  const { return val < rhs.val; }

    int val;
};
template<> struct std::hash<Foo> {
    size_t operator()(const Foo& f) const { return hash<int>{}(f.val); }
};

int main()
{
    std::unordered_map<Foo, int> mp;
    mp.insert(std::pair<Foo, int>{1, 50});
    std::cout << '\n';
    mp.insert(std::pair<const Foo, int>{2, 60});
    std::cout << '\n';

    std::cout << "exiting main()\n";
}

输出

Foo(int), val: 1
Foo(Foo &&)
~Foo(), val: 1

Foo(int), val: 2
Foo(const Foo &)
~Foo(), val: 2

exiting main()
~Foo(), val: 1
~Foo(), val: 2

问题#1

为什么 mp.insert(std::pair{1, 50}) 可以编译?是否发生隐式转换?

让我们按照我的看法来分解它。

  1. val 1 的 Foo(int) 在创建临时 std::pair 时被调用,val 1 被提供给 Foo ctor。 Foo(int), val: 1 被打印。
  2. std::pair insert(value_type && value) 被调用,临时对作为参数提供。 如何? std::unordered_map::value_type 是 std::pair<const Foo, int>。这里是否发生从 std::unordered_map 到 std::unordered_map 的隐式转换?如果是这样,是否应该在此处调用复制构造函数?
  3. 在地图中搜索到钥匙。未找到键,因此使用 std::pair{std::move(value)} 分配并初始化节点;演员。 Foo(Foo &&) 被打印。
  4. 临时pair对象被破坏。 ~Foo(), val: 1 被打印。

问题#2

为什么 Foo 的 move ctor 在第一次插入时被调用,而 Foo 的 copy ctor 在第二次插入时被调用?

c++ insert implicit-conversion unordered-map std-pair
1个回答
0
投票

问题1的答案

执行了隐式转换,但不是按照您期望的方式执行。使用

insert()
方法的不同重载,它本身执行转换。更准确地说:

  1. 正确
  2. 不正确。相反,会调用重载
    template<class P> iterator insert(P && value);
    ,因为该对不是
    const value_type &
    类型,也不是
    value_type &&
    node_type &&
    类型,这是这里其他可能的单参数重载。有关所有重载,请参阅 C++ 参考
    unordered_map::insert()
  3. 本质上是的。
  4. 是的。

问题2的答案

问题在于,只有当

std::pair<const Foo, int>
定义了 const 右值移动构造函数(即
Foo
)时,才可能进行
Foo(const Foo && other)
的移动。由于该特定构造函数不存在,编译器必须求助于该对的复制构造函数,这就是调用该构造函数的原因。

(并不是说你应该定义const右值移动构造函数。除非你在非常特定的情况下绝对需要它们,否则我会避免它们,因为它会增加额外的复杂性,从而使你的类的推理变得更加困难。)

一般注意事项

如果您想避免不必要的构造函数调用,您可能还想看看

unordered_map::emplace

© www.soinside.com 2019 - 2024. All rights reserved.