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

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

提问人:Foaly 提问时间:3/17/2016 最后编辑:BarryFoaly 更新时间:3/18/2016 访问量:6778

问:

我有以下问题。我有一个包含一个对象作为值的对象。现在,我想修改之前插入的对象。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 []

C++ C++11 引用 STL 无序映射

评论

2赞 EdChum 3/17/2016
我认为你需要一个默认的类构造函数,所以添加一个不带参数的ctor。PointPoint() : x(0), y(0) {}

答:

7赞 Barry 3/17/2016 #1

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

points[1]

在查找失败的情况下,需要能够默认插入 a(无论是否发生过查找失败 - 这是编译时要求,而不是运行时检查)。该要求不能满足,因为默认情况下不可构造。因此出现编译错误。如果要使用 ,则需要添加默认构造函数。PointPointPointunordered_map::operator[]

如果默认构造对您的使用没有意义 - 那么您根本无法使用,并且必须始终使用(或者如果您可以接受例外情况):Pointoperator[]findat()

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

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

评论

1赞 TartanLlama 3/17/2016
或者你可以用它来避免.您还需要使用而不是访问。emplacestd::make_pairpoints.at(1)points[1]
0赞 Yakk - Adam Nevraumont 3/17/2016
实际找不到点并不是导致错误的原因。它可能失败的可能性会导致编译器在失败的地方编写代码,从而导致编译时错误。这意味着下一个石灰会导致错误,尽管您先做了任何事情。简而言之,不适用于无法默认构造的类型。points[1].xpoints.insert[]
0赞 Barry 3/17/2016
@Yakk 我不认为我在暗示它确实如此。更好的措辞?
0赞 Yakk - Adam Nevraumont 3/18/2016
@Barry当然,但这意味着你没有解决 OP 的问题。下一行再次用于修改元素。我猜 OP 试图编写 MCVE,但没有注意到 MCVE 的早期部分产生了相同的错误,而不是有问题的行。在文本中,OP 显然关注这条线。[]points[1].x
0赞 Barry 3/18/2016
@Yakk 公平点。集中了答案,完全放弃了关于插入的部分。
2赞 Joseph Thomson 3/17/2016 #2

operator[] 如果给定键不存在任何元素,则就地构造映射类型的对象。在具有默认分配器的映射中,要求映射类型为默认可构造类型。更一般地说,映射类型必须是可构造的。operator[]

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

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

如果无法做到这一点,则必须使用其他函数来访问地图元素。

要访问现有的映射对象,可以使用 at,如果给定键不存在元素,它将引发 std::out_of_range 异常。

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

或者,您可以使用 find,它将迭代器返回到具有给定键的元素,或者返回映射中最后一个元素后面的元素(见结尾),如果不存在此类元素。

auto it = points.find(1);
if (it != points.end())
{
    it->second = 20.f;
}
0赞 Yakk - Adam Nevraumont 3/18/2016 #3

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

这种模式——我们传递一个函数来改变/访问一个容器的元素——正在从 Haskell monad 设计中窃取一个页面。我会让它返回而不是,其中是传入函数的返回类型,但这有点远。optional<X>boolX