在使用std::map时,使用指针访问值是否明智?

11

std::map::find返回指针并将其用作数据的方式,与获取数据的副本相比,是否存在危险性?

目前,我获取一个指向映射中条目的指针,并将其传递给另一个函数以显示数据。我担心项目移动会导致指针无效。这是个合理的担忧吗?

这是我的示例函数:

MyStruct* StructManagementClass::GetStructPtr(int structId)

{
    std::map<int, MyStruct>::iterator foundStruct;
    foundStruct= myStructList.find(structId);
    if (foundStruct== myStructList.end())
    {
        MyStruct newStruct;
        memset(&newStruct, 0, sizeof(MyStruct));
        myStructList.structId= structId;
        myStructList.insert(pair<int, MyStruct>(structId, newStruct));

       foundStruct= myStructList.find(structId);
   }

   return (MyStruct*) &foundStruct->second;

你的意思是迭代器而不是指针吧? - nims
你可以展示一些代码吗?你说的“从map.find返回指针”是指迭代器吗?这个迭代器会在调用.find()函数的函数退出之前一直有效。 - PermanentGuest
4个回答

9

毫无疑问,返回迭代器比返回指针更典型,尽管这可能没有多大区别。

就保持有效性而言:地图迭代器一直保持有效,直到除非所引用的项目从地图中移除/擦除。

当你在映射中插入或删除其他节点时,这可能导致映射中的节点重新排列。虽然这是通过操作节点之间的指针来完成的,但它会改变其他节点包含指向你关心的节点的指针的节点,但不会改变该特定节点的地址或内容,因此指向该节点的指针/迭代器仍然有效。


1
@nims:不会--除非它所指的项目从地图中被移除,否则迭代器仍然有效,因此您可以像使用任何其他迭代器一样使用它(好吧,无论如何是map中的任何其他迭代器)。 - Jerry Coffin
看起来在我的示例中,我返回的是指向迭代器->second的指针,而不是实际的map[key]->second。这是真的吗? - Jason
@Jason:是的,看起来差不多就是这样。但是我应该补充一下,这段代码在我看来并不是很好——它包含了一个不必要的强制类型转换、使用了memset函数,并且整个代码块与return your_map.insert(...).second;大致等价,因此它并没有展现出真正干净的代码风格。 - Jerry Coffin
我不指望代码会很干净。我对STL还不熟悉,所以在学习的过程中不断重写它。 我之前提到过,一开始没有进行类型转换,但是我正在追踪一个错误。memset有什么问题吗?我有一些字段可能不会全部填充,并且我知道它们不会被初始化为0,而我需要它们在未使用时的值为0。 - Jason
如果我返回std:map->second的副本(而不是指针),那么返回myListStruct[structId]是否可行?这假设键实际存在(在尝试返回之前,我会验证它是否存在)。 - Jason
显示剩余2条评论

3
只要您、您的代码和开发团队都理解 std::map 值的生命周期(在 insert 后有效,在 erase、clear、assign 或 operator= 后无效),那么使用 iterator、const_iterator、::mapped_type* 或 ::mapped_type const* 都是有效的。此外,如果返回值始终保证存在,则 ::mapped_type& 或 ::mapped_type const& 也是有效的。
就智慧而言,我更喜欢 const 版本,而不是可变版本,并且我更喜欢引用而不是指针或迭代器。
返回迭代器与指针是不好的:
1. 这暴露了实现细节。
2. 使用起来很麻烦,因为调用者必须知道如何解引用迭代器,结果是 std::pair,并且还必须调用 second 才能获取实际值。
- first 是用户可能不关心的键。
3. 确定迭代器是否无效需要知道 end(),这对于调用者并不明显。

2

这不是危险的——指针的有效期与迭代器或引用一样长。

然而,在你的特定情况下,我认为这也不是正确的方式。你的函数无条件地返回结果。它从不返回 null。那么为什么不返回一个引用呢?

另外,对于你的代码的一些评论。

std::map<int, MyStruct>::iterator foundStruct;
foundStruct = myStructList.find(structId);

为什么不将声明和赋值组合成初始化呢?如果您使用C++11支持,您只需要这样写:
auto foundStruct = myStructList.find(structId);

然后:

  myStructList.insert(pair<int, MyStruct>(structId, newStruct));
  foundStruct = myStructList.find(structId);

您可以使用make_pair简化插入操作。此外,您还可以避免冗余查找,因为insert返回一个指向新插入元素的迭代器(作为一对中的第一个元素)。

  foundStruct = myStructList.insert(make_pair(structId, newStruct)).first;

最后:

return (MyStruct*) &foundStruct->second;

永远不要使用C风格的强制类型转换。它可能不会产生您期望的结果。此外,当不需要时,请不要使用任何强制类型转换。&foundStruct->second已经具有MyStruct*类型,那么为什么要插入一个强制类型转换呢?它唯一做的事情就是隐藏一个需要更改的位置,如果您曾经更改了映射的值类型,就需要更改这个位置。


这个转换是我正在解决的一个调试问题。它最初并不存在,我会将其删除。 - Jason

0

是的,

如果您构建一个通用函数而不知道其用途,则返回指针(或迭代器)可能会变得无效,这可能会带来危险。

我建议采取以下两种方法之一:
1. 使用std::shared_ptr并返回它。(请参见下文)
2. 通过值返回结构体(可能会更慢)

//change the difination of the list to
std::map<int, std::shared_ptr<MyStruct>>myStructList;

std::shared_ptr<MyStruct> StructManagementClass::GetStructPtr(int structId)
{
    std::map<int, std::shared_ptr<MyStruct>>::iterator foundStruct;
    foundStruct = myStructList.find(structId);
    if (foundStruct == myStructList.end())
    {
        MyStruct newStruct;
        memset(&newStruct, 0, sizeof(MyStruct));
        myStructList.structId= structId;
        myStructList.insert(pair<int, shared_ptr<MyStruct>>(structId, shared_ptr<MyStruct>(newStruct)));

       foundStruct= myStructList.find(structId);
   }

   return foundStruct->second;

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