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测试用例也更简单,减少无谓的空指针分支的覆盖。

更多推荐