C++11中的unique_ptr和shared_ptr能相互转换吗?

154

C++11标准库是否提供将std::shared_ptr转换为std::unique_ptr,或者将std::unique_ptr转换为std::shared_ptr的实用工具?这个操作安全吗?


请定义“安全操作”。您寻找什么样的安全性?是生命周期管理安全还是线程安全? - jaggedSpire
2
“STL”并不是标准库的意思。STL与shared_ptr没有任何关系。 - curiousguy
1
@jaggedSpire 线程安全意味着您在不同的线程中使用了所有者,即使用计数不为1。 - curiousguy
@curiousguy 我知道。我的观点是,OP的问题中“安全”没有被很好地定义,他需要澄清他所指的“安全”是哪种类型,因为有多种类型。 - jaggedSpire
5个回答

255

std::unique_ptr是C++11表达独占所有权的一种方式,但它最有吸引力的特点之一是可以轻松高效地转换为std::shared_ptr

这是std::unique_ptr非常适合作为工厂函数返回类型的关键部分。工厂函数无法知道调用者是否希望对他们返回的对象使用独占所有权语义,还是共享所有权(即std::shared_ptr)更适合。通过返回std::unique_ptr,工厂为调用者提供了最有效的智能指针,但他们不会阻碍调用者将其替换为更灵活的“兄弟姐妹”。

不允许将std::shared_ptr转换为std::unique_ptr。一旦你将资源的生命周期管理委托给std::shared_ptr,就无法改变主意。即使引用计数为1,也无法重新获得对资源的所有权,以便让std::unique_ptr来管理它。

参考:《Effective Modern C++》。42 SPECIFIC WAYS TO IMPROVE YOUR USE OF C++11 AND C++14。Scott Meyers。

简而言之,您可以轻松高效地将std::unique_ptr转换为std::shared_ptr,但不能将std::shared_ptr转换为std::unique_ptr

例如:

std::unique_ptr<std::string> unique = std::make_unique<std::string>("test");
std::shared_ptr<std::string> shared = std::move(unique);
或:
std::shared_ptr<std::string> shared = std::make_unique<std::string>("test");

请注意,虽然编译器(至少不是gcc)不允许这样做,但如果您意外地(例如通过更改成员变量的指针类型)将std::unique_ptr分配给std::shared_ptr,编译器实际上不会阻止(甚至警告)。 - StefanQ
STL 参考:https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr - Ida
6
您认为不能将 std::unique_ptr 分配给 std::shared_ptr 吗?实际上,标准库定义了一个移动赋值运算符 template<class Y, class Deleter> shared_ptr& operator=(std::unique_ptr<Y, Deleter>&& r); ,用于将 std::unique_ptr 转换为 std::shared_ptr<T> - cuddlebugCuller
我想强调一下,从unique_ptrshared_ptr的转换是有效的,因为shared_ptr类了解unique_ptr并且具有接受rvalue reference的构造函数和赋值运算符(关键是rvalue reference可以保证unique_ptr不会再被使用)。这不是std::move的功劳,而是shared_ptr类的功劳。 - luca

2

我更愿意这样做。

std::unique_ptr<int> up_ = std::make_unique<int>();
std::shared_ptr<int> sp_ = std::move(up_);

0
尽管从`std::shared_ptr`转换为`std::unique_ptr`是设计上不可能的(因为这样会违反`std::unique_ptr`的单一引用不变性),但仍然可以将共享对象的内容移动到由独占指针管理的新实例中。例如:
std::shared_ptr<std::string> shared = std::make_shared<std::string>("test");
std::unique_ptr<std::string> unique = std::make_unique<std::string>(std::move(*shared));

在上面的第二行中,*shared的内容(即由shared指针管理的字符串)被移动到由unique指针管理的新的std::string实例中。这样做的副作用是,由shared管理的std::string对象处于“有效但未指定状态”,因为它已经被剥夺了其内容。但是,由于使受管理对象“唯一”的整个目的是防止它被其他任何地方访问,我认为这实际上是符合操作的精神的。

0
你可以将你的shared-pointer分配给一个unique-pointer。
可以提供一个自定义删除器,允许转移所有权。
尽管自定义删除器通常将所有权转移给将删除指针的对象,但指针也可以发送到其他任何地方。 使用自定义删除器强制执行已经明显的要求,即只有在shared pointer本身超出范围时才能转移所有权。
这里是一个最简示例:
#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> up;
    
    {
        std::shared_ptr<int> sp = std::shared_ptr<int>(new int (42),
            [&up] (int* ptr) {up = std::unique_ptr<int>(ptr); });
    }
    
    std::cout << "Unique-ptr = " << *up << "\n";
}

免责声明:我对这个主意是否可怕不发表评论!

-10
给定 unique_ptr u_ptr,创建 shared_ptr s_ptr:
std::shared_ptr<whatever> s_ptr(u_ptr.release());

走另一条路是不切实际的。
(正如许多人指出的那样,这个故事还有更多内容,值得阅读评论。)

38
以下是“正确”的写法: std::shared_ptr<whatever> s_ptr(std::move(u_ptr)); - emlai
14
这是一个过于学究的写法:“正确”的方法是:std::shared_ptr<whatever> s_ptr{std::move(u_ptr)}; - polyvertex
4
有什么方面不够安全? - nmr
10
@VioletGiraffe • 我想Polyvertex正在倡导使用新的初始化列表语法——这种语法避免了静默缩小转换,并且使用统一的语法进行成员初始化——作为一个好习惯。半斤八两? - Eljay
17
不安全,因为你可能会丢失存储在unique_ptr中的“Deleter”。 - Zang MingJie
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接