将键移出 std::map<> &&

4
我希望有一个名为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容器的键不能被修改,但如果这个map在键被修改后立即被销毁,是否仍然不允许修改键?


1
你能从 std::unique_ptr 切换到 std::shared_ptr 吗? - Daniel Langr
请看下面我如何解决这个问题的答案。 - Jarek C
3个回答

3

是的,它仍然是不允许的。如果您只打算在销毁映射后使用,则对键进行非const访问可能是安全的,但是按照标准并不能保证其安全性,且std::map接口也没有提供任何适用于右值引用的规则放松。

C++17以后,std::map提供了extract()函数,可以完全将一个键值对从映射中移除并将其作为“节点句柄”返回。此节点句柄提供对键的非const访问权限。因此,如果您将指针从该节点句柄中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
@Aconcagua 同意。我认为在这种情况下,我可能会使用const_cast的组合,并希望一切顺利。:-D - Sneftel
2
你有没有注意到OP不能使用C++17?我觉得他/她知道std::map::extract,只是不能使用它。你回答的第二部分对我来说完全是多余的,包括整个代码。(还要注意你的extractKeys函数只接受默认比较器和分配器模板参数的map,这不是OP的情况。) - Daniel Langr
不好意思,我错过了原帖中的那一部分(但请记住,SO的答案应该是普遍适用的,而不仅仅适用于原问题提出者)。这段代码旨在说明技术,而不是直接使用。 - Sneftel
它们应该适用于问题。 - Lightness Races in Orbit

3
注意:在我们的环境中,我不允许使用C++17函数std :: map :: extract()
可惜 - 它被引入就是为了解决这个问题。
是否可以使用const_cast,因为映射将被销毁?
不可以。
我想避免克隆MyObj
抱歉,您至少需要克隆键。
我知道为什么不能修改std :: map容器的键,但是对于一个在关键字修改后立即被销毁的映射,它是否仍然被禁止?
是的。
映射的内部机制无法知道其命运。


0

经过一些分析,答案说服了我应该避免使用const_cast。我意识到我的map的使用在代码中相当孤立,因此我可以进行小的重构以避免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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接