【问题标题】:How to move a non-copyable element from std::set to std::map using extract?如何使用提取将不可复制元素从 std::set 移动到 std::map?
【发布时间】:2020-12-05 07:04:19
【问题描述】:

考虑以下几点:

#include <set>
#include <map>

struct MyClass
{
    MyClass(int i);
    MyClass(MyClass const&) = delete;
    ~MyClass();

    bool operator<(const MyClass& r) const { return v < r.v; }
    int v;
};

void map_set_move_test()
{
    std::set<MyClass> s1, s2;
    std::map<int, MyClass> m1;

    s1.emplace(123);

    s2.insert(std::move(s1.extract(s1.begin())));

    // This fails
    m1.insert(std::move(std::make_pair(1, std::move(s2.extract(s2.begin()).value()))));
}

我使用std::set::extract 成功地将一个元素从std::set 移动到另一个std::set,如下所示:

s2.insert(std::move(s1.extract(s1.begin())));

但编译器不允许以这种方式将元素移动到std::map

m1.insert(std::move(std::make_pair(1, std::move(s2.extract(s2.begin()).value()))));

任何帮助将不胜感激。 Link to compiler explorer.

【问题讨论】:

  • 请比“不能”更具体。
  • 我不确定在不同容器类型之间传输实现定义的节点句柄是否是预期的用例?!
  • @lubgr 但是value()可以得到节点句柄的实际值,如果我没记错的话?
  • 不能加移动构造函数吗?
  • std::move(std::make_pair(...)); 对我来说没有多大意义。

标签: c++ c++17 move-semantics stdmap stdset


【解决方案1】:

这是一个有问题的例子,因为 MyClass 不仅是不可复制的,而且是隐含的 non-movable - 这可能不是你的意思。 (我认为 MyClass 由于您明确删除复制构造函数而变得不可移动;请参阅this answer。)

如果你确实让MyClass 可移动,像这样:

class MyClass : NonCopyable
{
public:
    MyClass(int i) : v(i) {
        std::cout << "constructor" << std::endl;
    }
    MyClass(MyClass&& other) : v(other.v) {
        std::cout << "move constructor" << std::endl;
    }
    ~MyClass() {
        std::cout << "destructor" << std::endl;
    }
    bool operator<(const MyClass& r) const {
        return v < r.v;
    }

    int v;
};

一切似乎都很顺利,当你写的时候,说。

m1.emplace(
    1,
    std::move( 
        s2.extract(s2.begin()).value() 
    )
);

您可以在 GodBolt 上看到此功能。


PS - 正如@DavisHerring 建议的那样,如果您不想覆盖现有值,您可能希望使用try_emplace() 方法。

【讨论】:

  • 谢谢你,你是对的!在这个问题的真实案例中,MyClass 类似于std::unique_ptr。这是我的测试示例错误。
  • 你已经回答了字面问题;我似乎已经回答了标题(或问题的精神?)。不知道哪个更好用!
【解决方案2】:

extract 的重点和insert 的相应重载是在没有内存分配的情况下操作基于节点的容器。 (其中一个有趣的操作是更改 std::map 键。)这在此处行不通,因为节点是不同的类型(即。,一个有一个键,另一个没有),所以你需要只需将值与 常规 insert(或更符合人体工程学的 try_emplace)一起使用。

【讨论】:

  • 嘿,有趣的措辞问题 - 集合包含键还是集合包含值?我一直认为它是前者,但显然你认为它是后者。
  • @JeJo 为什么它不起作用?这里所讨论的值来自 extract() 调用这一事实并不重要 - 只是你使 MyClass 可移动(所以这个调用可以很好地移动它)但在 OP 的情况下它不是(所以它不能)。
  • @Barry:标准将它们称为键,这在各种容器类型都在发挥作用的环境中当然是合适的(cf. Python,set(my_dict) 复制键,因为它们是可散列且唯一的)。我认为它们是值,因为它让所有容器都有值;此外,迭代 set(为 any 容器生成 value_type)非常常见,并且没有 operator[](当然,类似键的 find 可以用于规范化和count 用于成员测试)。
  • @Barry:还有两点:std::set 的节点句柄仅将其称为value_type(因为我猜你在上面看不到任何东西),而这个问题恰好想要使用set 元素作为mapped_type(尽管std::map&lt;…&gt;::value_typepair,我也倾向于将其称为“值”)。
猜你喜欢
  • 2012-06-10
  • 1970-01-01
  • 2021-12-23
  • 2012-09-28
  • 1970-01-01
  • 1970-01-01
  • 2016-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多