简体   繁体   中英

Using try_emplace with a shared_ptr

So I have a std::unordered_map<std::string, std::shared_ptr<T>> and I'd like to use try_emplace to add/reference elements. Previously, it was simply a std::unordered_map<std::string, T> and T has a constructor which just takes a std::string , so I was using myMap.try_emplace(myString, myString).first->second to reference the T and create it if necessary.

However, if I simply change it to myMap.try_emplace(myString, std::make_shared<T>(myString)).first->second , it, of course, constructs the T every time.

What's the most idiomatic way to get around this? To clarify, I'd like to construct the object on the heap with a shared pointer and insert the shared pointer into the unordered map if and only if there is not already a matching element in the map.

You can use operator[] instead:

auto &ptr = map[ key ];
if( !ptr ) ptr = std::make_shared<T>( myString );

Note: this solution came with assumption that you do not want to keep default constructed std::shared_ptr in the map. If that is the case, then you would need to use little more verbose code with try_emplace :

auto p = map.try_emplace( key, nullptr );
if( p.second ) p.first->second = std::make_shared<T>( myString );
else {
    if( !p.first->second ) { // handle nullptr }
}
auto &ptr = p.first->second;

Another option is to wrap the shared pointer in your own type. Then you can provide a constructor that takes a T and makes a shared_ptr<T> . This lets you use try_emplace and pass to it a T without having to have anything actually created if the object already exists. There is a lot of boiler plate involved in this though if you want to use the object as if it were a shared_ptr<T> .

Write a factory function wrapper:

template<class F>
struct auto_factory_t {
  F f;
  operator decltype(std::declval<F&&>()()) && {
    return std::move(f)();
  }
};
template<class F>
auto_factory_t<std::decay_t<F>> wrap_factory(F&&f){
  return {std::forward<F>(f)};
}

then

myMap.try_emplace(myString, wrap_factory([&]{return std::make_shared<T>(myString);})).first->second;

and done.

wrap_factory(f) returns an object containing f . It can be implicitly cast into the type f() returns, and when done so calls f() .

What you may do:

// C++17, else you have to move declaration outside of the `if`
// and retrieve it/inserted from pair result.
if (auto [it, inserted] = myMap.try_emplace(myString, nullptr); inserted) {
    it->second = std::make_shared<T>(myString);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM