我这样做:
class Something;
Something f();
...
std::shared_ptr<Something> ptr(new Something(f()));
但这感觉不对。而且它需要复制构造函数。还有更好的方法吗?
答案 0 :(得分:2)
您可以使用std::make_shared
。
由于以下原因,最好使用它:
此函数通常使用单个内存分配为T对象和shared_ptr的控制块分配内存(它是标准中的非绑定要求)。相反,声明std :: shared_ptr p(new T(Args ...))执行至少两次内存分配,这可能会产生不必要的开销。
答案 1 :(得分:2)
更好的方法是让f()
返回Something*
(使用new
分配)或shared_ptr<Something>
。否则,Something
返回的f()
将自动存储,并将其置于shared_ptr
无意义。理论上,您可以将shared_ptr
与自定义删除器一起使用,但这不会更改底层对象的存储类,并且您最有可能最终得到一个野指针。
如果您无法更改f()
,那么您使用动态存储制作副本的解决方案就是您所能做的一切。如果你可以给Something
一个移动构造函数,你至少可以降低制作副本的成本(假设它的价格足够昂贵,值得减少)。
但请参阅this answer了解副本为何不值得担心的原因。做任何你认为使代码最具可读性的东西。
答案 2 :(得分:2)
使用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
可复制,实际上与可接受的编译器没有区别,因为copy-ellision,也就是在new
/ {{分配的空间中直接构造返回值1}} / make_shared
。
所以,只要做你认为最好的事情。
标准明确允许复制椭圆。请注意,无论如何都必须可以访问复制构造函数:
12.8。复制和移动类对象§32
当满足某些条件时,允许实现省略类的复制/移动构造 对象,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下, 该实现将省略的复制/移动操作的源和目标视为两个不同的 引用相同对象的方式,以及对象的破坏发生在时间的晚期 当两个物体在没有优化的情况下被摧毁时.123这种复制/移动的省略 在下列情况下允许称为复制省略的操作(可以合并为 消除多份副本):
- 在具有类返回类型的函数的return语句中,当表达式是a的名称时 具有相同cvunqualified的非易失性自动对象(函数或catch子句参数除外) 键入函数返回类型,可以通过构造省略复制/移动操作 自动对象直接进入函数的返回值
- 在throw-expression中,当操作数是非易失性自动对象的名称时(除了 函数或catch子句参数),其范围不会超出最内层的末尾 封闭try-block(如果有的话),从操作数到异常的复制/移动操作 通过将自动对象直接构造到异常对象
中,可以省略对象(15.1) - 当复制/移动未绑定到引用(12.2)的临时类对象时 对于具有相同cv-unqualified类型的类对象,可以省略复制/移动操作 将临时对象直接构造到省略的复制/移动的目标中 - 当异常处理程序的异常声明(第15节)声明一个相同类型的对象时 (除了cv-qualification)作为异常对象(15.1),可以省略复制/移动操作 通过将异常声明视为异常对象的别名(如果程序的含义) 除了为声明的对象执行构造函数和析构函数之外,它将保持不变 异常声明。