std::unique_ptr with std::map

谁都会走 提交于 2021-01-28 07:27:25

问题


I have a std::map where the key is std::shared_ptr<Foo> and the value is std::unique_ptr<Bar> where Foo and Bar are very different classes from a third-party library. I am using this std::map object as an in-memory cache.

I am wondering what the best way of inserting a new entry into this map will be and then returned from a method, given that the Bar passed into the std::unique_ptr will already be constructed?

I currently have the following:

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;
}

EDIT

Thanks to the answer provided by Quentin, this is now my implementation:

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;
}

Thanks for all your help!


回答1:


return _cache.rbegin()->second.get(); does not do what you want, as std::map does not append elements but sorts them. However emplace returns an iterator to what it just inserted, so you only need:

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

Or even, since you don't actually need to store and copy the Bar, and you can also sacrifice foo:

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

I'd also personally flip the (it == _cache.end()) condition to make it an early return, but that's just a matter of taste.

Otherwise, what you have looks good to me.




回答2:


You tagged this as c++14, but for posterity I'll add a C++17 version:

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();
}

The try_emplace function will emplace its arguments if the map doesn't already contain that key. If the key already exists, no object is constructed. In either case an iterator to that key/value pair is returned. This function avoids the double lookup involved when you do find -> emplace/insert.

In our case we can't simply pass the arguments of try_emplace along so I've tried to be clever in delaying the construction of the object using this DelayedBar class. It only calls CreateNewBar when trying to cast to a std::unique_ptr<Bar> which only happens when try_emplace is trying to construct the object.

I have compiled this with GCC 8.2, Clang 7.0.0 and MSVC 19.16 (all via Compiler Explorer) and it compiles okay.



来源:https://stackoverflow.com/questions/54828756/stdunique-ptr-with-stdmap

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!