问题
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?
回答1:
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.
回答2:
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];
}
回答3:
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;
}
来源:https://stackoverflow.com/questions/58268530/moving-keys-out-of-stdmap