为啥 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&lt;P&gt;),这将编译得一样好,从而导致未定义的行为。

由于这个操作本质上是一个普通指针的所有权,这个操作必须明确地完成。这就是为什么将采用普通指针的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 &lt;&lt; *bar; 中访问它时,我们现在有未定义的行为,因为我们正在取消引用已删除的指针。

在您的情况下,您直接在调用站点创建指针,所以没关系,但正如您从示例中看到的那样,它可能会导致问题。

【讨论】:

【参考方案3】:

允许您使用指针参数直接调用函数,这很容易出错,因为您在调用站点不一定知道您正在从中创建共享指针。

void f(std::shared_ptr<int> arg);
int a;
f(&a); // bug

即使你忽略这一点,你也会在调用站点创建不可见的临时,创建shared_ptr 是相当昂贵的。

【讨论】:

除非你不使用new 创建它,否则你是deleteing 内存你没有newed。 @giò,它可能会导致过载模糊,并且如果它是隐含的,则制作 shared_ptr 比大多数人想要的要昂贵。对于其他类,它也可以是一个不明显的转换。例如,传递5 并将其转换为std::vector 将非常不直观。【参考方案4】:

我想知道为什么他们不允许更简单更好的...

当您变得更有经验并遇到更多写得不好、有缺陷的代码时,您的看法会发生变化。

shared_ptr&lt;&gt;,就像所有标准库对象的编写方式一样,尽可能难以导致未定义的行为(即难以找到浪费每个人时间并破坏我们生存意愿的错误)。

考虑:

#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&lt;&gt; 是函数的参数。

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() ?

为啥在java中不允许分配给'this'?

为啥 std::optional 不允许“移动构造和仅复制分配”类型的移动分配?

为啥 SQL Server 不允许我启用混合身份验证模式,以便我可以使用 sa 用户将角色分配给其他用户?

为啥 C++ 不允许重新绑定引用?

为啥我不能直接分配数据源?