在C++中,通过引用获取std::map中的对象是否安全?

22

我有一个像这样的映射表

map<int,object> objmap;
object& obj = objmap.find(num)->second;
object& obj2 = objmap[num];

无论我对对象做出什么更改,都必须在地图上反映出来。类似的事情在向量中无法完成,因为它会在需要更多空间时更改对象的位置。在 std::map 中这样做是否安全?是否建议这样做? 第二个版本会出现错误,因为我的对象没有空构造函数。如果我声明一个不做任何操作的空构造函数,这两行代码是否会以相同的方式工作?

5个回答

26
只要所讨论的对象没有从 map 中移除,那么它是安全的。一旦插入到 map 中,即使添加或删除其他元素,这些对象也不会移动。
object& obj = objmap.find(num)->second;

除非你确定该地图中存在一个键为 num 的元素,否则这可能是危险的。如果不确定,可以使用重载的 insert 函数,该函数返回一个迭代器和一个布尔值,指示是否向地图中插入了一个新元素或已经存在具有给定键的元素。

例如:

object& obj = objmap.insert( std::make_pair(num, object(arg1, arg2, argN)) ).first->second;

1
你知道在文档中如何查找这样的信息吗?事实上,我甚至不知道如何搜索这个答案:怎样正确地说“对象不会四处移动”?我猜规范必须准确定义这一点,因为这是向量和映射之间的一个重要区别。 - Flynsee
通常情况下,您应该查看此页面:https://en.cppreference.com/w/cpp/container/map 在这里,您可以检查修改器,并在浏览每个修改器后, 您应该会看到类似于以下内容的内容: “没有迭代器或引用被使无效”(这是针对插入等操作的)。 - user3063349

13

只要元素没有从映射中移除,这是安全的。

但是,第二行不太安全:

object& obj = objmap.find(num)->second;

如果在映射中没有键为num的元素,则find将返回objmap.end()。在解引用返回的迭代器之前,应该测试这种可能性:

const std::map<int, object>::iterator it = objmap.find(num);
if (it != objmap.end())
{
    object& obj = it->second;
    /* ... */
}

现在,如果目标并不是真的要找到内容而是要插入内容,那么调用operator[]是一种选择(但是,正如您已经注意到的,它需要值提供一个无参构造函数)。但是您必须理解这两件事是非常不同的:

  • find 仅仅 查找:如果未找到该键,则不会插入任何内容并返回end迭代器
  • operator[] 总是 返回映射中的值的引用:如果键不存在,则会进行插入(对于默认构造的值:因此需要构造函数)

4
如果你的问题是关于std::map在其可变函数中是否会使其迭代器无效,那么答案是否定的。标准保证std::map不会使其迭代器无效。

2
如果您希望映射中对象的更改得到反映,您可能需要将映射更改为存储指向对象的指针而不是对象本身。只要在引用之间不对对象进行任何使其无效的操作,引用就可以工作。
例如,以下代码将使用引用中断:
object& obj = objmap.find(num)->second;
objmap.erase(objmap.find(num)); // should check for objmap.end() - left out for simplicity
obj.DoSomething(); // this object has been destroyed, so the reference is invalid

1

做你正在做的事情是实现缓存的常见方式。

如果该项已经存在,则operator[]返回该项。如果不存在,则它将使用默认构造函数创建一个“空白”项,您可以在以后使用它进行编写。

当然,通常使用shared_ptr作为value_type,它在创建时将具有“空”值。在这种情况下,您需要通过引用获取shared_ptr,以便可以调用reset()。

与任何集合/缓存等一样,如果在多线程应用程序中执行,则必须注意线程安全问题。

我不确定您想知道的是是否可以将引用(或指向它的指针)存储在某个地方,并期望稍后添加其他项到映射时仍然有效,是的,它将有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接