【问题标题】:Moving keys out of std::map<> &&将键移出 std::map<> &&
【发布时间】:2019-10-07 11:18:45
【问题描述】:

我想假设getKeys() 函数从map 中获取不可复制的键:

class MyObj {
  // ... complex, abstract class...
};

struct Comparator { bool operator()(std::unique_ptr<MyObj> const &a, std::unique_ptr<MyObj> const &b); };

std::vector<std::unique_ptr<MyObj>> getKeys(std::map<std::unique_ptr<MyObj>, int, Comparator> &&map) {
  std::vector<std::unique_ptr<MyObj>> res;
  for (auto &it : map) {
    res.push_back(std::move(it.first));
  }
  return res;
}

但它不起作用,因为 it (.first) 中的密钥是 const。任何提示如何解决它?注意:在我们的环境中,我不允许使用 C++17 函数std::map::extract()

使用const_cast 是否可以,因为map 无论如何都会被破坏?

res.push_back(std::move(const_cast<std::unique_ptr<MyObj> &>(it.first)));

我想避免克隆MyObj

我知道为什么不能修改 std::map 容器的键,但是对于将在键修改后立即销毁的映射仍然不允许这样做吗?

【问题讨论】:

  • 你能从std::unique_ptr切换到std::shared_ptr吗?
  • 请看下面我如何解决这个问题的答案。

标签: c++ stdmap


【解决方案1】:

注意:在我们的环境中,我不允许使用 C++17 函数std::map::extract()

耻辱 - 引入它是为了解决这个问题。

是否可以使用const_cast,因为无论如何地图都会被破坏?

没有。

我想避免克隆MyObj

对不起;您至少需要克隆密钥。

我知道为什么不能修改 std::map 容器的键,但是对于将在键修改后立即销毁的映射仍然不允许这样做吗?

是的。

地图的内部机制无法知道它的命运正在等待。

【解决方案2】:

是的,它仍然是不允许的。如果您之后要销毁地图,对密钥的非 const 访问可能是安全的,但标准不能保证它是安全的,std::map 接口不提供任何放宽了适用于右值引用的规则。

自 C++17 以来,std::map 所做extract(),它将一个键值对完全从映射中分离出来并将其作为“节点句柄”返回.此节点句柄提供对密钥的非常量访问。因此,如果您要从该节点句柄中指向 move 指针,则最终销毁将发生在空指针上。

例子:

#include <utility>
#include <memory>
#include <vector>
#include <map>

template <typename K, typename V>
std::vector<K> extractKeys(std::map<K, V> && map)
{
    std::vector<K> res;
    while(!map.empty())
    {
        auto handle = map.extract(map.begin());
        res.emplace_back(std::move(handle.key()));
    }
    return std::move(res);
}

int main()
{
    std::map<std::unique_ptr<int>, int> map;
    map.emplace(std::make_pair(std::make_unique<int>(3), 4));

    auto vec = extractKeys(std::move(map));

    return *vec[0];
}

【讨论】:

  • 删除一个又一个键可能会导致大量的树重新平衡。但这似乎是唯一完全合法的方式再次取出钥匙......
  • @Aconcagua 同意。我认为在这种情况下,我可能会使用const_cast 的组合并希望获得最好的结果。 :-D
  • 你有没有注意到OP不允许使用C++17?在我看来,他/她知道std::map::extract,只是不能使用它。您回答的第二部分对我来说似乎完全多余,包括整个代码。 (另请注意,您的 extractKeys 函数将仅接受 maps 并带有默认的比较器和分配器模板参数,这不是 OP 的情况。)
  • 不,我错过了 OP 的那一点信息(但请记住,SO 答案旨在广泛适用,而不仅仅是适用于原始提问者)。该代码旨在说明该技术,未经修改不得使用。
  • 它们应该适用于这个问题
【解决方案3】:

答案说服我应该避免 const_cast-ing。经过一些分析,我意识到我的地图的使用在代码中是相当孤立的,所以我可以做一个小的重构来避免 const 问题。

结果如下:

class MyObj {
  // ... complex, abstract class...
};

struct Comparator { bool operator()(MyObj const *a, MyObj const *b); };

// key is a pointer only, value holds the key object and the effective "value"
struct KeyAndVal { std::unique_ptr<MyObj> key; int val; };
using MyMap = std::map<MyObj *, KeyAndVal, Comparator>;

// Example how emplace should be done
auto myEmplace(MyMap &map, std::unique_ptr<MyObj> key, int val) {
  auto *keyRef = key.get();  // to avoid .get() and move in one expr below
  return map.emplace(keyRef, KeyAndVal{ std::move(key), val });
}

std::vector<std::unique_ptr<MyObj>> getKeys(MyMap map) {
  std::vector<std::unique_ptr<MyObj>> res;
  for (auto &it : map) {
    res.push_back(std::move(it.second.key));
  }
  // here 'map' is destroyed but key references are still valid
  // (moved into return value).
  return res;
}

【讨论】:

    猜你喜欢
    • 2010-10-13
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多