将std :: map :: emplace与返回shared_ptr的函数一起使用是否正确?

时间:2018-10-10 16:27:20

标签: c++ stl shared-ptr

如果我有一些返回std::shared_ptr<T>的函数,我该如何在std::map<U, std::shared_ptr<T>>中插入该函数调用的结果:用insertmake_pair还是只用{ {1}}?

假设我已经知道没有重复的密钥,而且我是单线程的。

emplace

RVO是否可以与std::shared_ptr<Foo> bar() { return std::make_shared<Foo>(); } std::map<std::string, std::shared_ptr<Foo>> my_map; // Which makes more sense? my_map.emplace("key", bar()); // or my_map.insert(make_pair("key", bar())); 一起使用?

1 个答案:

答案 0 :(得分:3)

我认为是正确的。


RVO

我认为RVO只要在编译器中实现就可以正常工作,因为bar()返回时态对象,并且编译器知道从{{1 }}。 此外,在C ++ 17及更高版本中,在这种情况下,将确保RVO。

Is RVO (Return Value Optimization) applicable for all objects?

What are copy elision and return value optimization?


emplace与插入(DEMO

std :: map :: emplace

bar()

,RVO将my_map.emplace("key", bar()); 替换为bar(),并创建了std::make_shared<Foo>()。 然后将其转发,并在就地构建std::shared_ptr<Foo>的新元素时将std::shared_ptr<Foo> move-ctor 称为一次

std :: map ::使用std :: make_pair插入

my_map

,RVO再次将my_map.insert(make_pair("key", bar())); 替换为bar()

接下来,从N3337的20.3.2开始,到C ++ 11,并纠正了一些小错误,std::make_shared<Foo>()的类模板为

std::pair

模板ctor在哪里

namespace std{
  template<class T1, class T2>
  struct pair
  {
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;
    ...
    template<class U, class V> pair(U&& x, V&& y);
    ...
  }
}
     

效果:构造函数使用template<class U, class V> pair(U&& x, V&& y); 初始化first,并使用std::forward<U>(x)初始化second

此外,从20.3.3起

std::forward<V>(y)
     

返回template<class T1, class T2> pair<V1, V2> make_pair(T1&& x, T2&& y); ; ...

对于此定义,pair<V1, V2>(std::forward<T1>(x), std::forward<T2>(y))的典型实现被认为是这样的:

std::make_pair

,RVO将再次适用于namespace std{ template<class T1, class T2> inline pair<typename decay<T1>::type, typename decay<T2>::type> make_pair(T1&& x, T2&& y) { return pair<typename decay<T1>::type, typename decay<T2>::type> (std::forward<T1>(x), std::forward<T2>(y)); } } 。 实际上,如果我们使用C ++ 14和{-fno-elide-constructors“编译器选项编译DEMO,而该编译器选项会禁用g ++的RVO,则move-ctor调用的数量只会增加一个。

因此,在std::make_pair中,我预计会出现以下情况:

  • 1)RVO将std::make_pair("key", bar())替换为bar()
  • 2)RVO还将std::make_shared<Foo>()替换为std::make_pair
  • 3)创建std::pair::pair,然后将其转发到std::shared_prtr<Foo>
  • 4)std::pair::pair move-ctor 创建为元素std::shared_prtr<Foo>

最后,second的r值也被重载:

std::map::insert

再次出现

  • 5)调用了// since C++11 template< class P > std::pair<iterator,bool> insert( P&& value ); 中的 move-ctor

总之,我希望

std::shared_prtr<Foo>

调用my_map.insert(make_pair("key", bar())); 的move-ctor 两次

我的答案

由于在这两种情况下都根本没有std::shared_ptr<Foo>的副本,并且调用了几乎相同数量的移动指针,所以它们的性能几乎相同。

Moving an object into a map

insert vs emplace vs operator[] in c++ map

Is c++11 operator[] equivalent to emplace on map insertion?


注释1,通用参考文献

在C ++ 11及更高版本中,std::shared_ptr<Foo>的左右参数都是Scott Meyers所说的universal reference,也就是说,它们既可以接受l值又可以接受r值:

std::make_pair

因此,如果我们将l值传递给// until C++11 template< class T1, class T2 > std::pair<T1,T2> make_pair( T1 t, T2 u ); // since C++11 template< class T1, class T2 > std::pair<V1,V2> make_pair( T1&& t, T2&& u ); ,那么作为通用引用的结果,将调用std::make_pair的copy-ctor。


注意2,std :: map :: try_emplace

std::shared_ptr<Foo>始终构造std::map::emplace的新元素,即使该键已经存在。 如果密钥已经存在,则这些新实例将被销毁。 但是从C ++ 17开始,我们得到了

my_map

如果密钥template <class... Args> pair<iterator, bool> try_emplace(key_type&& k, Args&&... args); 已存在于容器中,则此函数不会构造args。 尽管我们已经知道当前问题中没有重复的密钥,但是如果我们不知道是否存在重复的密钥,则首选k

Is there any reason to use std::map::emplace() instead of try_emplace() in C++1z?