简体   繁体   中英

Moving keys out of std::map<> &&

I'd like to have let's say getKeys() function getting not-copiable keys out of a 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;
}

But it is not working because the key in it ( .first ) is const . Any tips how to solve it? Note: In our environment I'm not allowed to use C++17 function std::map::extract() .

Is it somehow ok to use const_cast because map will be destructed anyway?

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

I want to avoid cloning MyObj .

I know why keys of a std::map container cannot be modified but is it still disallowed for a map that is going to be destructed immediately after the key modification?

Yes, it's still disallowed. Non-const access to keys is probably safe if you're just going to destroy the map afterwards, but it's not guaranteed to be safe by the standard, and the std::map interface doesn't offer any sort of relaxation of the rules which applies to rvalue references.

What std::map does have since C++17, though, is extract() , which rips a key-value pair out of the map entirely and returns it as a "node handle". This node handle provides non-const access to the key. So if you were to move the pointer out of that node handle, the eventual destruction would happen to an empty pointer.

Example:

#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];
}

Note: In our environment I'm not allowed to use C++17 function std::map::extract() .

Shame - it was introduced to solve this problem.

Is it somehow ok to use const_cast because map will be destructed anyway?

No.

I want to avoid cloning MyObj .

Sorry; you'll need to clone the keys at least.

I know why keys of a std::map container cannot be modified but is it still disallowed for a map that is going to be destructed immediately after the key modification?

Yes.

The map's internal machinery has no way of knowing that its destiny awaits.

Answers persuaded me that I should avoid const_cast-ing. After some analysis I realized that usage of my map is quite isolated in the code so I could do a small refactor to avoid the const-issue.

Here is the result:

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM