简体   繁体   中英

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

I have the following problem. I have a std::unordered_map that contains an object as the value. Now I want to modify an object that I previously inserted.

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?

I get a weird long compile error about point not being able to be default constructed. The way I understand it operator [] returns a reference to the mapped type (aka the value), so why can't I modify it?

If the key isn't in the map, operator [] is required to create one. The expression

points[1]

needs to be able to default-insert a Point in case of lookup failure (regardless of whether lookup failure ever occurs - this is a compile-time requirement not a run-time check). That requirement cannot be satisfied by Point because Point is not default constructible. Hence the compile error. If you want to use unordered_map::operator[] , you'll need to add a default constructor.

If a default constructed Point doesn't make sense for your usage - then you simply cannot use operator[] and will have to use find throughout (or at() if you're okay with exceptions):

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 .

The easy solution is to add a default constructor to your class.

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

If this isn't possible, you will have to use other functions to access map elements.

To access an existing mapped object, you can using at , which will throw a std::out_of_range exception if no element exists with the given key.

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

Alternatively, you can use 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 ) if no such element exists.

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

operator[] cannot be used on a map or unordered_map without the data being default-constructible. This is because if the object is not found, it will create it via default-construction.

The easy solution is to make your type default-constructible.

if not:

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

then:

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

will do what points[1].x = 20.f; does without risking exception code or having to make Point default-constructible.

This pattern -- where we pass a function to mutate/access an element to a container -- is stealing a page from Haskell monad design. I would make it return optional<X> instead of bool , where X is the return type of the passed in function, but that is going a bit far.

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