将 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_backemplace_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_backrvalue 重载将通过调用 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_backemplace_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)

  1. some_vector.push_back(make_shared<ClassName>()); 右值引用传递给函数,push_back 只是调用 emplace_back.

    void push_back(value_type&& __x)
    { emplace_back(std::move(__x)); }