为什么 shared_ptr 不允许直接赋值

Why doesn't shared_ptr permit direct assignment

所以当使用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 class 这是一个允许直接赋值的智能指针,一切正常

Why [doesn't] shared_ptr permit direct assignment [copy initialization]?

因为是explicit,见here and here

I wonder what the rationale [is] behind it? (From a comment now removed)

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

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

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

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

语法:

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

允许将原始指针隐式转换为 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; 中访问它时,我们现在有未定义的行为,因为我们正在取消引用已删除的指针。

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

I wonder why they didn't allow a much simpler and better...

随着您变得更有经验并遇到更多编写糟糕的错误代码,您的意见将会改变。

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
}