【问题标题】: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=取指针。
  • 这不是作业。

标签: c++ c++11 shared-ptr smart-pointers


【解决方案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,见herehere

      我想知道它背后的基本原理是什么? (来自现已删除的评论)

      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);
      

      【讨论】:

        猜你喜欢
        • 2017-03-19
        • 2012-03-27
        • 2019-03-11
        • 2014-12-07
        • 1970-01-01
        • 2011-12-20
        • 1970-01-01
        • 1970-01-01
        • 2012-08-24
        相关资源
        最近更新 更多