为啥 shared_ptr 不允许直接分配
Posted
技术标签:
【中文标题】为啥 shared_ptr 不允许直接分配【英文标题】:Why doesn't shared_ptr permit direct assignment为什么 shared_ptr 不允许直接分配 【发布时间】:2016-05-17 12:00:15 【问题描述】:所以在使用shared_ptr<Type>
时可以这样写:
shared_ptr<Type> var(new Type());
我想知道为什么他们不允许更简单更好的 (imo):
shared_ptr<Type> var = new Type();
你需要使用.reset()
来实现这样的功能:
shared_ptr<Type> var;
var.reset(new Type());
我习惯了 OpenCV Ptr 类,它是一个智能指针,允许直接赋值并且一切正常
【问题讨论】:
因为std::shared_ptr
的构造函数取指针是explicit
,没有operator=
取指针。
这不是作业。
【参考方案1】:
语法:
shared_ptr<Type> var = new Type();
是copy initialization。这是用于函数参数的初始化类型。
如果允许,您可能会不小心将普通指针传递给采用智能指针的函数。此外,如果在维护期间,有人将 void foo(P*)
更改为 void foo(std::shared_ptr<P>)
,这将编译得一样好,从而导致未定义的行为。
由于这个操作本质上是一个普通指针的所有权,这个操作必须明确地完成。这就是为什么将采用普通指针的shared_ptr
构造函数设为explicit
- 以避免意外的隐式转换。
更安全、更有效的替代方案是:
auto var = std::make_shared<Type>();
【讨论】:
@giò 确实是个大问题。 @giò 共享指针假定它拥有它的指针(并且只与其他共享指针共享)。如果您将原始指针传递给共享指针,则很可能另一段代码假定它拥有原始指针,并会在某个时候尝试将其删除。但是共享指针也是如此!所以,是的,双重删除一点都不好。 我不会调用 std::make_shared safer... 与 std::weak_ptr 结合使用,std::make_shared 可能会产生不利影响: 对象占用的内存一直存在,直到所有弱所有者也被销毁。 “经典”方式效率较低,因为它进行了两次分配(对象 + 控制块),但是一旦所有 shared_ptr 被销毁,内存就会被释放,而不管剩余的weak_ptr。【参考方案2】:允许将原始指针隐式转换为std::shared_ptr
的问题可以通过
void foo(std::shared_ptr<int> bar) /*do something, doesn't matter what*/
int main()
int * bar = new int(10);
foo(bar);
std::cout << *bar;
现在,如果隐式转换有效,bar
指向的内存将被foo()
末尾的shared_ptr
析构函数删除。当我们在 std::cout << *bar;
中访问它时,我们现在有未定义的行为,因为我们正在取消引用已删除的指针。
在您的情况下,您直接在调用站点创建指针,所以没关系,但正如您从示例中看到的那样,它可能会导致问题。
【讨论】:
【参考方案3】:允许您使用指针参数直接调用函数,这很容易出错,因为您在调用站点不一定知道您正在从中创建共享指针。
void f(std::shared_ptr<int> arg);
int a;
f(&a); // bug
即使你忽略这一点,你也会在调用站点创建不可见的临时,创建shared_ptr
是相当昂贵的。
【讨论】:
除非你不使用new
创建它,否则你是delete
ing 内存你没有new
ed。
@giò,它可能会导致过载模糊,并且如果它是隐含的,则制作 shared_ptr
比大多数人想要的要昂贵。对于其他类,它也可以是一个不明显的转换。例如,传递5
并将其转换为std::vector
将非常不直观。【参考方案4】:
我想知道为什么他们不允许更简单更好的...
当您变得更有经验并遇到更多写得不好、有缺陷的代码时,您的看法会发生变化。
shared_ptr<>
,就像所有标准库对象的编写方式一样,尽可能难以导致未定义的行为(即难以找到浪费每个人时间并破坏我们生存意愿的错误)。
考虑:
#include<memory>
struct Foo ;
void do_something(std::shared_ptr<Foo> pfoo)
// ... some things
int main()
auto p = std::make_shared<Foo>(/* args */);
do_something(p.get());
p.reset(); // BOOM!
这段代码无法编译,这是件好事。因为如果这样做,程序将表现出未定义的行为。
这是因为我们将删除同一个 Foo 两次。
这个程序可以编译,并且格式正确。
#include<memory>
struct Foo ;
void do_something(std::shared_ptr<Foo> pfoo)
// ... some things
int main()
auto p = std::make_shared<Foo>(/* args */);
do_something(p);
p.reset(); // OK
【讨论】:
【参考方案5】:为什么[不]
shared_ptr
允许直接赋值[复制初始化]?
因为是explicit
,见here和here。
我想知道它背后的基本原理是什么? (来自现已删除的评论)
TL;DR,制作任何构造函数(或强制转换)explicit
是为了防止它参与隐式转换序列。
explicit
的要求可以更好地说明,shared_ptr<>
是函数的参数。
void func(std::shared_ptr<Type> arg)
//...
并称为;
Type a;
func(&a);
这将编译,并且如所写的那样是不受欢迎的和错误的;它不会像预期的那样运行。
将用户定义的(隐式)转换(强制转换运算符)添加到组合中会变得更加复杂。
struct Type
;
struct Type2
operator Type*() const return nullptr;
;
然后下面的函数(如果不是显式的)将编译,但提供了一个可怕的错误......
Type2 a;
func(a);
【讨论】:
以上是关于为啥 shared_ptr 不允许直接分配的主要内容,如果未能解决你的问题,请参考以下文章
为啥不推荐使用 std::shared_ptr::unique() ?
为啥 std::optional 不允许“移动构造和仅复制分配”类型的移动分配?