4

我的代码执行了以下操作:

  1. 从 a 中检索一个值mapwith operator[]
  2. 检查返回值以及是否NULL用于insert在地图中插入新元素。

0神奇的是,地图中出现了一个有值的元素。

经过几个小时的调试,我发现了以下内容:如果找不到键mapoperator[] 则插入一个新元素,而如果键存在,则insert 不会更改值

即使映射值类型的默认构造函数不存在,代码也会编译并operator[]插入0

有没有办法(例如从现在开始我可以遵循的一些编码约定)我可以防止这伤害我?

4

8 回答 8

7

我想显而易见的是要知道那些确实是索引运算符的语义,所以你不应该用它来测试容器中元素的存在。

相反,使用find().

于 2012-05-21T12:06:04.170 回答
7

有没有办法(例如从现在开始我可以遵循的一些编码约定)我可以防止这伤害我?

这可能听起来很尖刻,但是:通过阅读文档。

由于您所做的是地图的某种预期行为,因此您无法采取任何措施来防范它。

您将来可以注意的一件事是以下内容。在您的第二步中,您做错了什么:

检查返回值,如果为 NULL,则使用 insert 在地图中插入新元素。

这不适用于 C++ 标准库函数(除了 C 兼容函数和):标准库new处理指针,尤其是空指针,因此检查NULL(or 0or nullptr) 很少有意义。operator [](除此之外,地图首先返回指针是没有意义的。它显然会返回元素类型(或者更确切地说,是对它的引用))。

事实上,标准库主要使用迭代器end(),所以如果有的话,通过与容器的比较来检查迭代器的有效性。

不幸的是,NULL编译后的代码(检查)NULL实际上是一个在 C++ 中等于的宏,0因此您可以将其与整数进行比较。

通过引入nullptr具有不同类型的关键字,C++11 变得更安全,因此将其与整数进行比较不会编译。所以这是一个有用的编码约定:永远不要使用NULL,而是在启用 C++11 支持的情况下编译并使用nullptr

于 2012-05-21T12:10:35.607 回答
4

实际上,当您调用 时operator [],如果未找到该键的值,则会插入值初始化的默认值。

如果您不希望发生这种情况,则必须检查find

if ( mymap.find(myKey) == mymap.end() )
{
    //the key doesn't exist in a map
}

仅当它是指向指针的映射(或值初始化为 0 的类型,但您非常具体)时,operator []才会返回的值。NULLNULL

于 2012-05-21T12:05:15.437 回答
4

即使映射值类型的默认构造函数不存在,代码也会编译

这绝对是错误的。operator[]如果默认构造函数不存在,则应该无法编译。其他任何事情都是您实施的错误。

您的代码应该只使用insert过一次。

于 2012-05-21T12:06:31.433 回答
2

std::map 的最新实现还有一个.at(const Key& key)成员函数,它检查值是否存在,std::out_of_range如果未找到键则返回异常。

http://en.cppreference.com/w/cpp/container/map/at

于 2012-05-21T12:09:43.017 回答
0

不要使用 [] 运算符,而是在地图上调用 find 。如果未找到匹配项,则不会插入条目。它返回一个迭代器到找到的项目。

于 2012-05-21T12:05:45.787 回答
0

以下是地图查找和插入用例的几个示例,您有时想要处理已经存在的项目的情况,查看旧值是什么等。

class Foo
{
    // Use a typedef so we can conveniently declare iterators
    // and conveniently construct insert pairs
    typedef map<int, std::string> IdMap;
    IdMap id_map;

    // A function that looks up a value without adding anything
    void one(int id)
    {
        IdMap::iterator i = id_map.find(id);

        // See if an entry already exists
        if (i == id_map.end())
            return; // value does not exist

        // Pass the string value that was stored in the map to baz
        baz(i->second);
    }

    // A function that updates an existing value, but only if it already exists
    bool two(int id, const std::string &data)
    {
        IdMap::iterator i = id_map.find(id);

        if (i == id_map.end())
            return false;

        i->second = data;
        return true;
    }

    // A function that inserts a value only if it does NOT already exist
    // Returns true if the insertion happened, returns false if no effect
    bool three(int id, const std::string &data)
    {
        return id_map.insert(IdMap::value_type(id, data)).second;
    }

    // A function that tries to insert if key doesn't already exist,
    // but if it does already exist, needs to get the current value
    void four(int id, const std::string &data)
    {
        std::pair<IdMap::iterator,bool> i =
            id_map.insert(IdMap::value_type(id, data));

        // Insertion worked, don't need to process old value
        if (i->second)
            return true;

        // Pass the id to some imaginary function that needs
        // to know id and wants to see the old string and new string
        report_conflict(id, i->first->second, data);
    }
};

出于懒惰或无知,程序员经常对 进行多次冗余调用operator[],或者一次调用find然后对 进行冗余调用operator[],或者对 调用find然后对 进行冗余调用。insert如果您了解地图的语义,那么有效地使用地图是非常容易的。

于 2013-05-28T02:21:24.970 回答
0

operator[] 实际上返回一个 Value& 所以如果您确定要插入元素,您可以执行以下操作:

map<Key, Value*> my_map;

Value& entry = my_map[key];  // insert occurs here, using default constructor.
if (entry == nullptr) entry = my_new_entry;  // just changing the value

额外的好处是您只需在地图中查找一次。

于 2013-07-15T15:57:58.213 回答