在 C++ 中修改 std::unordered_map 元素的值

Modify the value of a std::unordered_map element in C++

我有以下问题。我有一个 std::unordered_map 包含一个对象作为值。现在我想修改我之前插入的对象。

class Point
{
public:
    Point(float _x, float _y) : x(_x), y(_y) {}

    float x;
    float y;
};
std::unordered_map<int, Point> points;
// ... add some values to it ...
points[1].x = 20.f; // error?

我收到一个关于无法默认构造点的奇怪的长编译错误。我理解它的方式 operator [] returns 对映射类型(又名值)的引用,为什么我不能修改它?

如果密钥不在地图中,operator [] 需要 创建一个。表达式

points[1]

需要能够在查找失败的情况下默认插入 Point(无论查找失败是否曾经发生——这是编译时要求,而不是 运行 时检查). Point 无法满足该要求,因为 Point 不是默认可构造的。因此编译错误。如果你想使用 unordered_map::operator[] ,你需要添加一个默认构造函数。

如果默认构造的 Point 对您的使用没有意义 - 那么您根本无法使用 operator[] 并且必须在整个过程中使用 find(或 at() 如果你同意例外的话):

auto it = points.find(1);
if (it != points.end()) {
   it->second.x = 20.f;
}

points.at(1).x = 20.f; // can throw

operator[] constructs an object of mapped type in-place if no element exists with the given key. In a map with a default allocator, operator[] requires the mapped type to be default constructible. More generally, the mapped type must be emplace constuctible.

简单的解决方案是将默认构造函数添加到您的 class。

Point() : Point(0.f, 0.f) {}

如果这不可能,您将不得不使用其他函数来访问地图元素。

要访问现有映射对象,如果不存在具有给定键的元素,您可以使用 at, which will throw a std::out_of_range 异常。

points.at(1).x = 20.f;

或者,如果不存在这样的元素,您可以使用 find, which returns an iterator to the element with the given key, or to the element following the last element in the map (see end)。

auto it = points.find(1);
if (it != points.end())
{
    it->second = 20.f;
}

operator[] 不能在 mapunordered_map 上使用,除非数据是可默认构造的。这是因为如果找不到对象,它将通过默认构造创建它。

简单的解决方案是使您的类型可默认构造。

如果没有:

template<class M, class K, class F>
bool access_element( M& m, K const& k, F&& f ) {
  auto it = m.find(k);
  if (it == m.end())
    return false;
  std::forward<F>(f)(it->second);
  return true;
}

然后:

std::unordered_map<int, Point> points;
points.emplace(1, Point(10.f, 10.f));
access_element(points, 1, [](Point& elem){
  elem.x = 20.f;
});

将做 points[1].x = 20.f; 所做的事情,而不会冒异常代码的风险,也不必使 Point 默认可构造。

这种模式——我们将函数传递给 mutate/access 将元素传递给容器——正在从 Haskell monad 设计中窃取页面。我会将其设为 return optional<X> 而不是 bool,其中 X 是传入函数的 return 类型,但这有点过分了。