问题
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