stl map operator []不好?

时间:2012-01-10 09:08:51

标签: c++ stl map operators

我的代码审核人员指出,使用地图的operator []非常糟糕并导致错误:

map[i] = new someClass;    // potential dangling pointer when executed twice

或者

if (map[i]==NULL) ...      // implicitly create the entry i in the map 

虽然我理解读取API之后的风险insert()更好,因为它检查重复,因此可以避免悬空指针发生,我不明白,如果处理得当,为什么{{ 1}}根本无法使用?

我选择map作为我的内部容器,因为我想使用它快速且自我解释的索引功能。

我希望有人可以与我争辩或站在我这边:)

9 个答案:

答案 0 :(得分:11)

operator[]可能有用的唯一时间(我能想到的)是你想要设置一个键的值(如果它已经有一个值就覆盖它),你知道它是可以安全地覆盖(应该是因为你应该使用智能指针,而不是原始指针)并且对于默认构造来说是便宜的,并且在某些情况下,值应该< strong>无投掷建设和任务。

e.g。 (类似于你的第一个例子)

std::map<int, std::unique_ptr<int>> m;
m[3] = std::unique_ptr<int>(new int(5));
m[3] = std::unique_ptr<int>(new int(3)); // No, it should be 3.

否则根据上下文有几种方法可以做到这一点,但我建议总是使用一般解决方案(这样你就不会出错)。

找到一个值并创建它(如果它不存在):

<强> 1。一般解决方案(推荐它始终有效)

std::map<int, std::unique_ptr<int>> m;
auto it = m.lower_bound(3);
if(it == std::end(m) || m.key_comp()(3, it->first))
   it = m.insert(it, std::make_pair(3, std::unique_ptr<int>(new int(3)));

<强> 2。价格低廉的默认构造

std::map<int, std::unique_ptr<int>> m;
auto& obj = m[3]; // value is default constructed if it doesn't exists.
if(!obj)
{
   try
   {
      obj = std::unique_ptr<int>(new int(3)); // default constructed value is overwritten.
   }
   catch(...)
   {
      m.erase(3);
      throw;
   }
}

第3。使用廉价的默认构造和无投入值

std::map<int, my_objecct> m;
auto& obj = m[3]; // value is default constructed if it doesn't exists.
if(!obj)
   obj = my_objecct(3);

注意:您可以轻松地将常规解决方案包装到辅助方法中:

template<typename T, typename F>
typename T::iterator find_or_create(T& m, const typename T::key_type& key, const F& factory)
{
    auto it = m.lower_bound(key);
    if(it == std::end(m) || m.key_comp()(key, it->first))
       it = m.insert(it, std::make_pair(key, factory()));
    return it;
}

int main()
{
   std::map<int, std::unique_ptr<int>> m;
   auto it = find_or_create(m, 3, []
   {
        return std::unique_ptr<int>(new int(3));
   });
   return 0;
}

请注意,我传递模板化的工厂方法而不是create case的值,这样在找到值时不会产生任何开销,也不需要创建。由于lambda作为模板参数传递,编译器可以选择内联它。

答案 1 :(得分:5)

你是对的,map::operator[]必须谨慎使用,但它非常有用:如果你想在地图中找到一个元素,如果没有,那就创建它:

someClass *&obj = map[x];
if (!obj)
    obj = new someClass;
obj->doThings();

地图中只有一次查找。 如果new失败,您可能希望从地图中删除NULL指针,当然:

someClass *&obj = map[x];
if (!obj)
    try
    {
        obj = new someClass;
    }
    catch (...)
    {
        obj.erase(x);
        throw;
    }
obj->doThings();

当然,如果你想找到一些东西,但不想插入它:

std::map<int, someClass*>::iterator it = map.find(x); //or ::const_iterator
if (it != map.end())
{
    someClass *obj = it->second;
    obj->doThings();
}

答案 2 :(得分:2)

诸如“使用地图的运算符[]非常糟糕的声明”应该始终是几乎宗教信仰的警示标志。但与大多数此类声明一样,某些地方潜伏着一些真相。然而,这里的真相与C ++标准库中的几乎任何其他构造一样:小心并知道你在做什么。你可以(意外地)滥用几乎所有东西。

一个常见问题是潜在的内存泄漏(假设您的地图拥有对象):

std::map<int,T*> m;
m[3] = new T;
...
m[3] = new T;

这会明显泄漏内存,因为它会覆盖指针。在这里正确使用插入也不容易,许多人犯了一个会泄漏的错误,例如:

std::map<int,T*> m;
minsert(std::make_pair(3,new T));
...
m.insert(std::make_pair(3,new T));

虽然这不会覆盖旧指针,但它不会插入新指针而且会泄漏它。插入的正确方法是(可能使用智能指针更好地增强):

std::map<int,T*> m;
m.insert(std::make_pair(3,new T));
....
T* tmp = new T;
if( !m.insert(std::make_pair(3,tmp)) )
{
    delete tmp;
}

但这也有些难看。我个人更喜欢这样简单的案例:

std::map<int,T*> m;

T*& tp = m[3];
if( !tp )
{
    tp = new T;
}

但这可能与您的代码审核人员不允许op []使用的个人偏好量相同......

答案 3 :(得分:1)

  1. operator []可以避免插入,因为同样的原因 你在问题中提到过。它不检查重复键 并覆盖现有的。
  2. operator []中进行搜索时,大多数时间都不会使用
  3. std::map。 因为,如果您的map中不存在密钥,那么operator []默默地创建新密钥并初始化它(通常为 0)。在所有情况下这可能都不是优选的。一个人应该使用 只有在需要创建密钥时才会[],如果密钥不存在。

答案 4 :(得分:1)

根本不是[]的问题。将原始指针存储在容器中是一个问题。

答案 5 :(得分:0)

如果你的地图就像这样:

std::map< int, int* >

然后你输了,因为下一个代码片段会泄漏内存:

std::map< int, int* > m;
m[3] = new int( 5 );
m[3] = new int( 2 );

  

如果处理得当,为什么不能使用[]?

如果您正确测试了代码,那么您的代码仍然无法通过代码审查,因为您使用了原始指针。

除此之外,如果使用得当,使用map::operator[]没有任何问题。但是,使用插入/查找方法可能会更好,因为可能会进行静默地图修改。

答案 6 :(得分:0)

 map[i] = new someClass;    // potential dangling pointer when executed twice

这里,问题不是地图的operator[],而是缺少智能指针。 你的指针应该存储在一些RAII对象中(比如一个智能指针),它可以获得所分配对象的所有权,并确保它被释放。

如果您的代码审核人员忽略了这一点,而是说您应该使用operator[],请为他们购买一本优秀的C ++教科书。

if (map[i]==NULL) ...      // implicitly create the entry i in the map 

这是真的。但那是因为operator[]的行为方式不同。显然,你不应该在它做错事的情况下使用它。

答案 7 :(得分:0)

通常问题是operator []隐式创建与传入键相关联的值,如果键尚未出现,则在映射中插入新对。从那时起,这可能会打破你的逻辑,例如当您搜索某个密钥是否存在时。

map<int, int> m;
if (m[4] != 0) {
  cout << "The object exists" << endl; //furthermore this is not even correct 0 is totally valid value
} else {
  cout << "The object does not exist" << endl;
}
if (m.find(4) != m.end()) {
  cout << "The object exists" << endl; // We always happen to be in this case because m[4] creates the element
}

我建议仅在您知道将引用地图中已存在的密钥时使用运算符[](事实证明这种情况并非如此罕见)。

答案 8 :(得分:0)

地图的operator[]本身没有任何问题,只要它 语义对应于你想要的。问题是定义你的 想要(并且知道operator[]的确切语义)。有时候 当条目隐式创建具有默认值的新条目时 不存在正是你想要的(例如计算文本中的单词) 文档,其中++ countMap[word]就是你所需要的;有 许多其他时间,它不是。

代码中一个更严重的问题可能是存储指针 在地图上。更自然的解决方案可能是使用map <keyType, someClass>,而不是map <keyType, SomeClass*>。但是,这一次 取决于所需的语义;例如,我使用了很多map 在程序启动时初始化一次,指向静态 实例。如果您map[i] = ...处于初始化循环中, 在启动时执行一次,可能没有问题。如果是的话 在代码中的许多不同位置执行,可能有一个 问题。

问题的解决方案不是禁止operator[](或映射到 指针)。解决方案是首先指定确切的语义 你需要。如果std::map没有直接提供它们(很少 写)一个小的包装类,它定义了你的确切语义 希望,使用std::map来实现它们。因此,你的包装 operator[]可能是:

MappedType MyMap::operator[]( KeyType const& key ) const
{
    MyMap::Impl::const_iterator elem = myImpl.find( key );
    if ( elem == myImpl.end() )
        throw EntryNotFoundError();
    return elem->second;
}

或:

MappedType* MyMap::operator[]( KeyType const& key ) const
{
    MyMap::Impl::const_iterator elem = myImpl.find( key );
    return elem == myImpl.end()
        ?   NULL   //  or the address of some default value
        :   &elem->second;
}

同样,您可能希望使用insert而不是operator[] if 你真的想插入一个尚未存在的值。

我几乎从未见过你立刻插入的情况 new将对象放入地图中。使用new和{。}的常见原因 delete是有问题的对象具有一定的特定生命周期 他们自己(并且不可复制 - 虽然不是绝对的规则,如果 你是new一个支持复制和分配的对象,你是 可能做错了什么)。当映射类型是指针时, 然后指向的对象是静态的(并且地图是更多或 初始化后不太常量),或插入和删除 在类的构造函数和析构函数中完成。 (但这只是 一般规则;肯定有例外。)