将 std::map::emplace 与返回 shared_ptr 的函数一起使用是否正确?
Is it correct to use std::map::emplace with a function returning a shared_ptr?
如果我有一些 returns 和 std::shared_ptr<T>
的函数,我应该如何在 std::map<U, std::shared_ptr<T>>
中插入该函数调用的结果:使用 insert
和 make_pair
或者只是 emplace
?
假设我已经知道没有重复键,并且我是单线程的。
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()));
RVO 是否适用于 std::map::emplace
?
我认为是正确的。
RVO
我认为只要在您的编译器中实现 RVO 就可以正常工作,因为 bar()
returns 时间对象和编译器知道什么对象将从 bar()
返回。
此外,在 C++17 及更高版本中,RVO 在这种情况下得到保证。
Is RVO (Return Value Optimization) applicable for all objects?
What are copy elision and return value optimization?
放置与插入 (DEMO)
std::map::emplace
在
my_map.emplace("key", bar());
,RVO 将 bar()
替换为 std::make_shared<Foo>()
并创建了 std::shared_ptr<Foo>
。
然后转发 std::shared_ptr<Foo>
的 move-ctor 在 [=26] 的新元素的就地构造中调用 一次 =].
std::map::insert with std::make_pair
在
my_map.insert(make_pair("key", bar()));
,RVO 再次将 bar()
替换为 std::make_shared<Foo>()
。
接下来,从N3337的20.3.2开始,C++11修正了一些小错误,std::pair
的class模板是
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);
Effects: The constructor initializes first
with std::forward<U>(x)
and second
with std::forward<V>(y)
.
此外,从 20.3.3
template<class T1, class T2>
pair<V1, V2> make_pair(T1&& x, T2&& y);
Returns: pair<V1, V2>(std::forward<T1>(x), std::forward<T2>(y))
;...
因此,此定义的 std::make_pair
的典型实现被认为是这样的:
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));
}
}
并且 RVO 将再次为 std::make_pair
工作。
事实上,如果我们用 C++14 编译 DEMO 和禁用 g++ 的 RVO 的编译器选项“-fno-elide-constructors”,那么 move-ctor 调用的数量只会增加一个。
因此在 std::make_pair("key", bar())
中,我预计会出现以下情况:
- 1) RVO 将
bar()
替换为 std::make_shared<Foo>()
,
- 2) RVO 也将
std::make_pair
替换为 std::pair::pair
,
- 3)创建
std::shared_prtr<Foo>
,然后转发到std::pair::pair
,
- 4)
std::shared_prtr<Foo>
由 move-ctor. 创建为元素 second
最后,std::map::insert
也为 r 值重载:
// since C++11
template< class P >
std::pair<iterator,bool> insert( P&& value );
又来了
- 5) 调用
std::shared_prtr<Foo>
的 move-ctor。
综上所述,我预计
my_map.insert(make_pair("key", bar()));
调用 std::shared_ptr<Foo>
的 move-ctor 两次。
我的回答
由于在这两种情况下都没有完成 std::shared_ptr<Foo>
的副本并且调用了几乎相同数量的移动操作者,因此它们的性能几乎相同。
Moving an object into a map
insert vs emplace vs operator[] in c++ map
注1,通用参考
在C++11及以上版本中,std::make_pair
的左参数和右参数都是Scott Meyers所说的universal reference,即它们可以接受左值和右值:
// 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
,那么 std::shared_ptr<Foo>
的复制函数将作为通用引用的结果被调用。
注2,std::map::try_emplace
std::map::emplace
总是构造 my_map
的新元素,即使键已经存在。
如果密钥已经存在,则这些新实例将被销毁。
但是从 C++17 开始,我们得到
template <class... Args>
pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
如果键 k
已存在于容器中,则此函数不会构造 args
。
虽然我们已经知道本题不存在重复键,但如果不知道是否存在重复键,则优先选择std::map::try_emplace
如果我有一些 returns 和 std::shared_ptr<T>
的函数,我应该如何在 std::map<U, std::shared_ptr<T>>
中插入该函数调用的结果:使用 insert
和 make_pair
或者只是 emplace
?
假设我已经知道没有重复键,并且我是单线程的。
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()));
RVO 是否适用于 std::map::emplace
?
我认为是正确的。
RVO
我认为只要在您的编译器中实现 RVO 就可以正常工作,因为 bar()
returns 时间对象和编译器知道什么对象将从 bar()
返回。
此外,在 C++17 及更高版本中,RVO 在这种情况下得到保证。
Is RVO (Return Value Optimization) applicable for all objects?
What are copy elision and return value optimization?
放置与插入 (DEMO)
std::map::emplace
在
my_map.emplace("key", bar());
,RVO 将 bar()
替换为 std::make_shared<Foo>()
并创建了 std::shared_ptr<Foo>
。
然后转发 std::shared_ptr<Foo>
的 move-ctor 在 [=26] 的新元素的就地构造中调用 一次 =].
std::map::insert with std::make_pair
在
my_map.insert(make_pair("key", bar()));
,RVO 再次将 bar()
替换为 std::make_shared<Foo>()
。
接下来,从N3337的20.3.2开始,C++11修正了一些小错误,std::pair
的class模板是
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);
Effects: The constructor initializes
first
withstd::forward<U>(x)
andsecond
withstd::forward<V>(y)
.
此外,从 20.3.3
template<class T1, class T2> pair<V1, V2> make_pair(T1&& x, T2&& y);
Returns:
pair<V1, V2>(std::forward<T1>(x), std::forward<T2>(y))
;...
因此,此定义的 std::make_pair
的典型实现被认为是这样的:
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));
}
}
并且 RVO 将再次为 std::make_pair
工作。
事实上,如果我们用 C++14 编译 DEMO 和禁用 g++ 的 RVO 的编译器选项“-fno-elide-constructors”,那么 move-ctor 调用的数量只会增加一个。
因此在 std::make_pair("key", bar())
中,我预计会出现以下情况:
- 1) RVO 将
bar()
替换为std::make_shared<Foo>()
, - 2) RVO 也将
std::make_pair
替换为std::pair::pair
, - 3)创建
std::shared_prtr<Foo>
,然后转发到std::pair::pair
, - 4)
std::shared_prtr<Foo>
由 move-ctor. 创建为元素
second
最后,std::map::insert
也为 r 值重载:
// since C++11
template< class P >
std::pair<iterator,bool> insert( P&& value );
又来了
- 5) 调用
std::shared_prtr<Foo>
的 move-ctor。
综上所述,我预计
my_map.insert(make_pair("key", bar()));
调用 std::shared_ptr<Foo>
的 move-ctor 两次。
我的回答
由于在这两种情况下都没有完成 std::shared_ptr<Foo>
的副本并且调用了几乎相同数量的移动操作者,因此它们的性能几乎相同。
Moving an object into a map
insert vs emplace vs operator[] in c++ map
注1,通用参考
在C++11及以上版本中,std::make_pair
的左参数和右参数都是Scott Meyers所说的universal reference,即它们可以接受左值和右值:
// 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
,那么 std::shared_ptr<Foo>
的复制函数将作为通用引用的结果被调用。
注2,std::map::try_emplace
std::map::emplace
总是构造 my_map
的新元素,即使键已经存在。
如果密钥已经存在,则这些新实例将被销毁。
但是从 C++17 开始,我们得到
template <class... Args>
pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
如果键 k
已存在于容器中,则此函数不会构造 args
。
虽然我们已经知道本题不存在重复键,但如果不知道是否存在重复键,则优先选择std::map::try_emplace