如何从按值返回的函数初始化std::shared_ptr

How to initialize a std::shared_ptr from a function returning by value?

本文关键字:std 初始化 shared ptr 函数 返回      更新时间:2023-10-16

我这样做:

class Something;
Something f();
...
std::shared_ptr<Something> ptr(new Something(f()));

但这感觉不对。此外,它还需要复制构造函数。有没有更好的办法?

使用std::make_shared避免显式调用new。类似地,使用std::make_unique

make_shared可能更有效,因为它可以在一个块中为智能指针和对象分配计数器。

不过,在构造对象之后,但在它被安全嵌入智能指针之前,至少有一种方法可以让语句引起异常,否则它不会发挥自己的作用。所述异常否则将导致内存泄漏。

不良行为示例:

void f(std::shared_ptr<int> a, std::shared_ptr<int> b);
f(std::shared_ptr<int>(new int(0)), std::shared_ptr<int>(new int(4)));

和纠正:

f(std::make_shared<int>(0), std::make_shared<int>(4));

现在,有人建议您不按值返回Something,而是作为动态分配的指针。对于您的用例,实际上与可接受的编译器没有区别,只要Something是可复制的,由于复制省略,即直接在new/make_shared/make_unique分配的空间中构造返回值。所以,就做你认为最好的事情吧。

Copy-ellision是标准明确允许的。只要注意复制构造函数必须是可访问的:

12.8。复制和移动类对象§32

当满足某些条件时,允许实现省略类的复制/移动构造对象,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为两个不同的对象指代同一对象的方式,以及对该对象的破坏发生在时代的后期如果没有优化,这两个对象将被销毁拷贝/移动的省略在下列情况下允许进行称为复制省略的操作(可以合并为删除多个副本):
-在返回类型为类的函数的返回语句中,当表达式是类的名称时具有相同cvunqualified的非易失性自动对象(函数或catch子句参数除外)类型作为函数返回类型,则可以通过构造来省略复制/移动操作自动对象直接转化为函数的返回值
-在抛出表达式中,当操作数是非易失性自动对象的名称(除了(函数或catch子句参数),其作用域不会扩展到最内层的末尾封闭try-block(如果有的话),从操作数到异常的复制/移动操作可以通过将自动对象直接构造为异常对象
来省略对象(15.1)。-当一个临时类对象没有被绑定到引用(12.2)将被复制/移动对于具有相同cv- undefined类型的类对象,可以通过直接将临时对象构造为省略的copy/move
的目标对象-当异常处理程序的异常声明(第15条)声明相同类型的对象时(cv-qualification除外)作为例外对象(15.1),复制/移动操作可以省略通过将异常声明作为异常对象的别名来处理程序的意义除了执行构造函数和析构函数外,将保持不变exception-declaration。

您可以使用std::make_shared。最好使用它,原因如下:

这个函数通常为T对象和shared_ptr的控制块分配一个内存分配(这是标准中的非绑定要求)。相反,std::shared_ptr p(new T(Args…))声明至少执行两次内存分配,这可能会导致不必要的开销。

更好的方法是让f()返回Something*(与new分配)或shared_ptr<Something>。否则,f()返回的Something将具有自动存储,并且将其放入shared_ptr中没有意义。从理论上讲,您可以使用带有自定义删除器的shared_ptr,但这不会改变底层对象的存储类,而且您很可能最终得到一个野指针。

如果你不能改变f(),你的解决方案是用动态存储创建一个副本。如果你可以给Something一个move构造函数,你至少可以减少复制的成本(假设它足够昂贵,值得减少)。

但看看为什么副本不值得担心的答案。