微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

将 make_shared 与 emplace_back 和 push_back 一起使用 - 有什么区别吗?

如何解决将 make_shared 与 emplace_back 和 push_back 一起使用 - 有什么区别吗?

some_vector.push_back(make_shared<ClassName>());
some_vector.emplace_back(make_shared<ClassName>());

我想检查一下我的理解是否正确,对于 make_shared 以及通常对于返回对象的所有其他函数,这两个调用是相同的。这里 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 强制从头开始,即只接受自定义的非复制/移动构造函数以实现这种清晰的分离,但即使它以可接受的方式成为可能,那就太好了,它会与许多模板上下文场景(快进)发生冲突,并且容易出错的用法仍然是可能的,尽管更加明确。

根据我上面的例子,代码重构是一个值得怀疑的重点。简单想象一下,前面的代码使用了原始指针,即实际的底层错误已经存在并被 emplace_back -usage 隐藏了。它也会被 emplace_back -usage 隐藏在那里,但不会在您将代码更新为共享指针方式后立即隐藏。

即使它与您的特定特定用例无关,我认为这里值得一提,因为人们应该对两种方法之间的潜在差异完全有信心。

感谢 Human-Compiler 在评论中提及我在此处使用的错误术语。

,

要理解这个问题,让我们首先考虑调用 std::make_shared<class_type>(),
它返回临时对象,这意味着 Xvalue 是一个可以重用资源的 eXpiring 值。现在让我们看看这两种情况,

some_vector.push_back(make_shared<ClassName>());

std::vector 有两个 push_back 重载,其中之一接受 rvalue 引用,即
constexpr void push_back( T&& value );
这意味着 value 被移动到新元素中,但是如何呢? rvaluepush_back 重载将通过调用 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)); }
    

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。