将try_emplace与shared_ptr一起使用

时间:2018-06-19 14:50:58

标签: c++

所以我有一个std::unordered_map<std::string, std::shared_ptr<T>>,我想使用try_emplace添加/引用元素。以前,它只是一个std::unordered_map<std::string, T>,而T的构造函数只需要一个std::string,所以我使用myMap.try_emplace(myString, myString).first->second来引用T并创建它如有必要。

但是,如果我简单地将其更改为myMap.try_emplace(myString, std::make_shared<T>(myString)).first->second,它当然每次都会构造T

解决此问题的最惯用的方法是什么?为了明确起见,我想使用共享指针在堆上构造对象,并且仅在映射中没有匹配元素的情况下,才将共享指针插入到无序映射中。

5 个答案:

答案 0 :(得分:6)

您可以改用operator[]

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

注意:此解决方案附带您不希望在地图中保留默认构造的std::shared_ptr的假设。如果是这种情况,那么您将需要在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;

答案 1 :(得分:2)

另一个选择是将共享指针包装为您自己的类型。然后,您可以提供一个使用T并生成shared_ptr<T>的构造函数。这样,您就可以使用try_emplace并将其传递给T,而不必在对象已经存在的情况下实际创建任何东西。但是,如果您要像使用shared_ptr<T>那样使用对象,则涉及很多样板。

答案 2 :(得分:2)

写一个工厂函数包装器:

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

然后

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

完成。

wrap_factory(f)返回一个包含f的对象。可以将其隐式转换为f()返回的类型,然后调用f()

答案 3 :(得分:2)

您可以做什么:

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

答案 4 :(得分:0)

现代C ++ 17方法

template <class factory>
class deferred_factory {
public:
    deferred_factory(factory&& mfactory) : mFactory{std::move(mfactory)} {}

    // Wrap invocation of @c mFactory in an appropriate conversion operator.
    /* implicit */ operator std::invoke_result_t<factory>() {
        return mFactory();
    }

private:
    factory mFactory;
};

// Deduction guide for lvalue reference factories.
template <class factory>
deferred_factory(const factory&) -> deferred_factory<const factory&>;

说明

基本技巧是延迟初始化,即调用factory。这是通过从另一个类型(deferred_factory)初始化值类型来完成的,该类型可以隐式转换为所需的值类型。

转换运算符完全返回 与直接调用factory相同的类型。由于使用C ++ 17的mandatory copy/move elision,因此不会产生任何多余的副本(不需要进一步的&& / std::move)。

有了class template argument deduction,就不需要其他帮助来创建deferred_factory了。最后的deduction guide允许将现有工厂重复使用多个deferred_factory,而这些工厂只将引用包装到原始工厂。

用法

在OP的背景下

myMap.try_emplace("key", deferred_factory{[]{return std::make_shared<T>("value");}});

在且仅当以前不存在时才将“密钥”插入myMap中。 shared_ptr<T>仅在插入时创建。


此答案是Yakk - Adam Nevraumont的现代变体。全部归功于他。