将 make_shared 与 emplace_back 和 push_back 一起使用 - 有什么区别吗?
Using make_shared with emplace_back and push_back - any difference?
some_vector.push_back(make_shared<ClassName>());
some_vector.emplace_back(make_shared<ClassName>());
我想检查一下我的理解是否正确,对于 make_shared
和一般来说对于 returns 一个对象的所有其他函数,这两个调用是相同的。这里make_shared
会新建一个shared_ptr
,然后这个指针会被移到push_back
和emplace_back
的容器中。这是正确的,还是会有一些不同?
vector<T>::push_back
有一个 T&&
重载,与 vector<T>::emplace_back
T&&
版本相同。
不同之处在于 emplace_back
会将任何参数集完美转发给 T
的构造函数,而 push_back
只接受 T&&
或 T const&
.当您实际传递 T&&
或 T const&
时,它们行为的标准规范是相同的。
我想在 Yakk 的回答中添加一个小细节。
emplace_back
案例的参数转发可能会引入令人怀疑的可怕错误 - 即使对于共享指针的向量 - 如果不特别小心使用,请参见实例
#include <vector>
#include <memory>
struct SimpleStruct {};
auto main() -> int
{
std::vector<std::shared_ptr<SimpleStruct>> v;
SimpleStruct a;
v.emplace_back(std::addressof(a)); // compiles, UB
v.push_back(std::addressof(a)); // fails to compile
}
是的,这是一个极端的例子,因为像这样的代码应该始终特别小心地使用或受到一般质疑,但它强调,如果没有人应该只参考 emplace_back
复制对象已经在手边,它的唯一目的是添加到向量中,并参考 push_back
所有常见的 copy/move-construction 情况。如果 language/standard 库可以从头开始为 emplace_back
强制执行此操作,即只接受自定义 non-copy/move 构造函数以实现这种清晰的分离,但即使在可接受的范围内是可能的方式,它会与许多模板上下文场景(快速转发)发生冲突,并且容易出错的用法仍然是可能的,尽管更明确一些。
根据我上面的例子,代码重构是这里疑惑的一个重点。简单地想象一下,之前的代码使用了原始指针,即实际的底层错误已经存在并被 emplace_back
-usage 隐藏。它也将被 push_back
隐藏 - 那里的用法但不会在您将代码更新为共享指针方式后立即隐藏。
即使它与您的特定用例无关,我认为这里值得一提,因为人们应该对这两种方法之间的潜在差异充满信心。
感谢 Human-Compiler 在评论中提到我之前使用的错误术语。
要理解这个问题,让我们首先考虑调用 std::make_shared<class_type>()
、
的结果是什么
它 returns 临时对象,这意味着 Xvalue
一个 eXpiring
值,其资源可以被重用。现在让我们看看这两种情况,
some_vector.push_back(make_shared<ClassName>());
std::vector
有两个 push_back
重载,其中一个接受 rvalue
引用,即
constexpr void push_back( T&& value );
这意味着 value
被移动到新元素中,但是如何呢? push_back
的 rvalue
重载将通过调用 shared_ptr( shared_ptr&& r ) noexcept;
移动构造新值,r
的所有权将被占用,并且 r
变为空。
some_vector.emplace_back(make_shared<ClassName>());
在 emplace_back( Args&&... args )
中,元素是通过 std::allocator_traits::construct
通过 args..
到 std::forward<Args>(args)...
的完美转发构造的,这意味着 rvalue
将完美转发并导致相同的移动构造函数 shared_ptr( shared_ptr&& r ) noexcept;
被调用。
结论是,push_back
和emplace_back
的效果是一样的。
但是上面解释的事情并没有发生,因为 compiler
出现在画面中,它的作用是执行优化,这意味着它不是创建临时对象并将它们移动到其他对象中,而是直接创建对象就位。
两种情况下的结果相同。
下面包含编译器优化理论的支持代码,如您所见,输出仅打印一个构造函数调用。
#include <iostream>
using std::cout;
using std::endl;
class Object{
public:
explicit Object(int );
Object(const Object& );
Object(Object&& );
};
Object::Object(int ){
cout<< __PRETTY_FUNCTION__<< endl;
}
Object::Object(const Object& ){
cout<< __PRETTY_FUNCTION__<< endl;
}
Object::Object(Object&& ){
cout<< __PRETTY_FUNCTION__<< endl;
}
int main(){
[[maybe_unused]] Object obj(Object(1));
}
输出:
Object::Object(int)
some_vector.push_back(make_shared<ClassName>());
右值引用传递给函数,push_back 只是调用 emplace_back.
void push_back(value_type&& __x)
{ emplace_back(std::move(__x)); }
some_vector.push_back(make_shared<ClassName>());
some_vector.emplace_back(make_shared<ClassName>());
我想检查一下我的理解是否正确,对于 make_shared
和一般来说对于 returns 一个对象的所有其他函数,这两个调用是相同的。这里make_shared
会新建一个shared_ptr
,然后这个指针会被移到push_back
和emplace_back
的容器中。这是正确的,还是会有一些不同?
vector<T>::push_back
有一个 T&&
重载,与 vector<T>::emplace_back
T&&
版本相同。
不同之处在于 emplace_back
会将任何参数集完美转发给 T
的构造函数,而 push_back
只接受 T&&
或 T const&
.当您实际传递 T&&
或 T const&
时,它们行为的标准规范是相同的。
我想在 Yakk 的回答中添加一个小细节。
emplace_back
案例的参数转发可能会引入令人怀疑的可怕错误 - 即使对于共享指针的向量 - 如果不特别小心使用,请参见实例
#include <vector>
#include <memory>
struct SimpleStruct {};
auto main() -> int
{
std::vector<std::shared_ptr<SimpleStruct>> v;
SimpleStruct a;
v.emplace_back(std::addressof(a)); // compiles, UB
v.push_back(std::addressof(a)); // fails to compile
}
是的,这是一个极端的例子,因为像这样的代码应该始终特别小心地使用或受到一般质疑,但它强调,如果没有人应该只参考 emplace_back
复制对象已经在手边,它的唯一目的是添加到向量中,并参考 push_back
所有常见的 copy/move-construction 情况。如果 language/standard 库可以从头开始为 emplace_back
强制执行此操作,即只接受自定义 non-copy/move 构造函数以实现这种清晰的分离,但即使在可接受的范围内是可能的方式,它会与许多模板上下文场景(快速转发)发生冲突,并且容易出错的用法仍然是可能的,尽管更明确一些。
根据我上面的例子,代码重构是这里疑惑的一个重点。简单地想象一下,之前的代码使用了原始指针,即实际的底层错误已经存在并被 emplace_back
-usage 隐藏。它也将被 push_back
隐藏 - 那里的用法但不会在您将代码更新为共享指针方式后立即隐藏。
即使它与您的特定用例无关,我认为这里值得一提,因为人们应该对这两种方法之间的潜在差异充满信心。
感谢 Human-Compiler 在评论中提到我之前使用的错误术语。
要理解这个问题,让我们首先考虑调用 std::make_shared<class_type>()
、
的结果是什么
它 returns 临时对象,这意味着 Xvalue
一个 eXpiring
值,其资源可以被重用。现在让我们看看这两种情况,
some_vector.push_back(make_shared<ClassName>());
std::vector
有两个 push_back
重载,其中一个接受 rvalue
引用,即 constexpr void push_back( T&& value );
这意味着 value
被移动到新元素中,但是如何呢? push_back
的 rvalue
重载将通过调用 shared_ptr( shared_ptr&& r ) noexcept;
移动构造新值,r
的所有权将被占用,并且 r
变为空。
some_vector.emplace_back(make_shared<ClassName>());
在 emplace_back( Args&&... args )
中,元素是通过 std::allocator_traits::construct
通过 args..
到 std::forward<Args>(args)...
的完美转发构造的,这意味着 rvalue
将完美转发并导致相同的移动构造函数 shared_ptr( shared_ptr&& r ) noexcept;
被调用。
结论是,push_back
和emplace_back
的效果是一样的。
但是上面解释的事情并没有发生,因为 compiler
出现在画面中,它的作用是执行优化,这意味着它不是创建临时对象并将它们移动到其他对象中,而是直接创建对象就位。
两种情况下的结果相同。
下面包含编译器优化理论的支持代码,如您所见,输出仅打印一个构造函数调用。
#include <iostream>
using std::cout;
using std::endl;
class Object{
public:
explicit Object(int );
Object(const Object& );
Object(Object&& );
};
Object::Object(int ){
cout<< __PRETTY_FUNCTION__<< endl;
}
Object::Object(const Object& ){
cout<< __PRETTY_FUNCTION__<< endl;
}
Object::Object(Object&& ){
cout<< __PRETTY_FUNCTION__<< endl;
}
int main(){
[[maybe_unused]] Object obj(Object(1));
}
输出:
Object::Object(int)
some_vector.push_back(make_shared<ClassName>());
右值引用传递给函数,push_back 只是调用 emplace_back.void push_back(value_type&& __x) { emplace_back(std::move(__x)); }