C++的map[key]使用陷阱--用放大镜看代码开发
·
class FooClass
{
public:
void CreateValue(int32_t key)
{
fooMap[key] = std::make_shared<Value>();
if (fooMap[key] == nullptr) {
return;
}
// do something use fooMap[key]
}
void UseValue(int32_t key)
{
if (fooMap[key] != nullptr) {
fooMap[key]->DoSomething();
}
}
private:
std::unordered_map<int32_t, std::shared_ptr<Value>> fooMap;
};
先看看上面这段代码,最明显的问题是在UseValue中不能仅仅判断fooMap[key] != nullptr。对于map的使用,应该首先通过find方法,判断map中的元素是否存在,然后再使用获取到的value。
一个神奇的现象,fooMap[key] != nullptr这句话,其实已经给map中添加了一个元素,只不过这个元素大概率是nullptr。这个是本文重点想强调的一个关注点。
第二版改进:
class FooClass
{
public:
void CreateValue(int32_t key)
{
fooMap[key] = std::make_shared<Value>();
if (fooMap[key] == nullptr) {
return;
}
// do something use fooMap[key]
}
void UseValue(int32_t key)
{
auto it = fooMap.find(key);
if (it != fooMap.end()) {
fooMap[key]->DoSomething();
}
}
private:
std::unordered_map<int32_t, std::shared_ptr<Value>> fooMap;
};
这个版本其实也是有问题的,只是找到了map中的元素存在,并不能一定保证其非空,因此使用前还是需要判空。
第三版改进:
class FooClass
{
public:
void CreateValue(int32_t key)
{
fooMap[key] = std::make_shared<Value>();
if (fooMap[key] == nullptr) {
return;
}
// do something use fooMap[key]
}
void UseValue(int32_t key)
{
auto it = fooMap.find(key);
if (it != fooMap.end() && fooMap[key] != nullptr) {
fooMap[key]->DoSomething();
}
}
private:
std::unordered_map<int32_t, std::shared_ptr<Value>> fooMap;
};
这一版还是不完美,因为在find之后,如果find到元素,但是有可能在执行fooMap[key]的瞬间,元素被删除,此时操作的就不是find的元素。是一个随机值(见第二版修改说明)。为了保证状态的一致性,一是可以对fooMap的操作加锁,同时要使用find出来的it值进行操作,而不是使用fooMap[key]操作。
第四版改进:
class FooClass
{
public:
void CreateValue(int32_t key)
{
fooMap[key] = std::make_shared<Value>();
if (fooMap[key] == nullptr) {
return;
}
// do something use fooMap[key]
}
void UseValue(int32_t key)
{
auto it = fooMap.find(key);
if (it != fooMap.end() && it->second != nullptr) {
it->second->DoSomething();
}
}
private:
std::unordered_map<int32_t, std::shared_ptr<Value>> fooMap;
};
这版完美了吗?其实也不太完美。我们想下,为什么从map中find出来的结果一定需要判空呢?我们能不能保证map中的值一定是有效的呢?
这需要看看CreateValue方法,我们会发现,同样的是因为使用了fooMap[key],导致map中的value值有可能为空。
因此我们需要第五版改进:
class FooClass
{
public:
void CreateValue(int32_t key)
{
auto value = std::make_shared<Value>();
if (value == nullptr) {
return;
}
fooMap[key] = value;
// do something use fooMap[key]
}
void UseValue(int32_t key)
{
auto it = fooMap.find(key);
if (it != fooMap.end()) {
it->second->DoSomething();
}
}
private:
std::unordered_map<int32_t, std::shared_ptr<Value>> fooMap;
};
这样的话,如果std::make_shared<Value>()失败,就不会往fooMap中放东西,反过来讲,只要能从fooMap中find到,一定非空,这样就能保证fooMap中能find到的元素,一定是有效的,不需要再判空。
到这里,个人认为是相对完美的一个方案。他带来了哪些好处?
- 更少的代码元素,意味着更小的维护成本;
- 开发新需求的代价更小,出错的概率更低;
- UT测试用例也更简单,减少无谓的空指针分支的覆盖。
更多推荐
所有评论(0)