std::unique_ptr 和 std::map

std::unique_ptr with std::map

我有一个 std::map,其中键为 std::shared_ptr<Foo>,值为 std::unique_ptr<Bar>,其中 FooBar 非常不同 类 来自第三方库。我正在使用这个 std::map 对象作为内存缓存。

我想知道将新条目插入此映射然后从方法返回的最佳方法是什么,假设传递到 std::unique_ptrBar 已经构建?

我目前有:

class SomeClass
{
public:

    const Bar* TryGetBarValue(std::shared_ptr<Foo> foo)
    {
        auto it = _cache.find(foo);

        if(it == _cache.end())
        {
            Bar bar = ThirdPartLibrary::CreateBar();
            _cache.emplace(foo, std::make_unique<Bar>(bar));
            return _cache.rbegin()->second.get();
        }

        //return result as raw ptr from unique_ptr
        return it->second.get();
    }

private:
    std::map<std::shared_ptr<Foo>, std::unique_ptr<Bar>> _cache;
}

编辑

感谢 Quentin 提供的答案,现在这是我的实现:

class SomeClass
{
public:

    const Bar* TryGetBarValue(std::shared_ptr<Foo> foo)
    {
        auto it = _cachedImages.find(texture);

        if (it != _cachedImages.end())
        {
            return it->second.get();
        }

        return _cachedImages.emplace(
                std::move(texture), 
                std::make_unique<sf::Image>(texture->copyToImage())
            ).first->second.get(); 
        }

private:
    std::map<std::shared_ptr<Foo>, std::unique_ptr<Bar>> _cache;
}

感谢您的帮助!

return _cache.rbegin()->second.get(); 不会做您想要的,因为 std::map 不会 append 元素,而是 sorts 它们.然而 emplace returns 是它刚刚插入的迭代器,所以你只需要:

return _cache.emplace(foo, std::make_unique<Bar>(bar))->first->second.get();

甚至,因为你实际上不需要存储和复制 Bar,你也可以牺牲 foo:

return _cache.emplace(
    std::move(foo),
    std::make_unique<Bar>(ThirdPartLibrary::CreateBar())
)->first->second.get();

我个人也会翻转 (it == _cache.end()) 条件,使其提前 return,但这只是个人喜好问题。

要不然你这个我觉得还不错

你将其标记为 c++14,但为了后代,我将添加一个 C++17 版本:

const Bar* TryGetBarValue(std::shared_ptr<Foo> foo)
{
    struct DelayedBar
    {
        operator std::unique_ptr<Bar>() const { return std::make_unique<Bar>(thirdpartyLibrary::CreateBar()); }
    };
    return _cache.try_emplace(std::move(foo), DelayedBar()).first->second.get();
}

如果地图尚未包含该键,try_emplace 函数将放置其参数。如果键已经存在,则不构造任何对象。在任何一种情况下,都会返回 key/value 对的迭代器。此函数避免了执行 find -> emplace/insert.

时涉及的双重查找

在我们的例子中,我们不能简单地传递 try_emplace 的参数,所以我试图聪明地使用这个 DelayedBar class 延迟对象的构造.它仅在尝试转换为 std::unique_ptr<Bar> 时调用 CreateNewBar,这仅在 try_emplace 尝试构造对象时发生。

我已经使用 GCC 8.2、Clang 7.0.0 和 MSVC 19.16(全部通过编译器资源管理器)编译了它并且编译正常。