所以我有一个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
。
解决此问题的最惯用的方法是什么?为了明确起见,我想使用共享指针在堆上构造对象,并且仅在映射中没有匹配元素的情况下,才将共享指针插入到无序映射中。
答案 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的现代变体。全部归功于他。