Перемещение ключей из std :: map <> && - PullRequest
4 голосов
/ 07 октября 2019

Хотелось бы, скажем, функцию 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 не могут быть изменены, но все еще не разрешено для карты, которая будет уничтожена сразу после ключамодификация

Ответы [ 3 ]

3 голосов
/ 07 октября 2019

Примечание. В нашей среде мне запрещено использовать функцию C ++ 17 std::map::extract().

Позор - она ​​была введена для решения этой проблемы.

Разве можно нормально использовать const_cast, потому что карта все равно будет уничтожена?

Нет.

Я хочу избежать клонирования MyObj.

Извините;вам нужно как минимум клонировать ключи.

Я знаю, почему ключи контейнера std::map не могут быть изменены, но все еще не разрешено для карты, которая будет уничтожена сразу послеизменение ключа?

Да.

Внутренние механизмы карты не могут знать, что его ждет судьба.

3 голосов
/ 07 октября 2019

Да, это все еще запрещено. Неконстантный доступ к ключам безопасен , вероятно , если впоследствии вы просто собираетесь уничтожить карту, но он не гарантированно безопасен по стандарту, а интерфейс std::map не предлагает никакого видаослабления правил, которые применяются к ссылкам rvalue.

Что std::map имеет , так как C ++ 17, тем не менее, является 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];
}
0 голосов
/ 07 октября 2019

Ответы убедили меня, что я должен избегать const_cast-ing. После некоторого анализа я понял, что использование моей карты довольно изолированно в коде, поэтому я мог сделать небольшой рефакторинг, чтобы избежать проблемы с константой.

Вот результат:

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