std::shared_ptr
定义于头文件 <memory>
。
声明
template< class T > class shared_ptr;
描述
std::shared_ptr
是一个智能指针,它通过指针保留对象的共享所有权。
多个 shared_ptr
对象可以拥有同一个对象。当以下任一情况发生时,对象将被销毁并解除内存分配:
- 最后一个拥有该对象的
shared_ptr
被销毁; - 最后一个拥有该对象的
shared_ptr
通过operator=
或reset()
被赋值给另一个指针。 - 对象使用 delete 表达式或在构造 shared_ptr 时提供的自定义删除器销毁。
shared_ptr
可以共享对象的所有权,同时存储指向另一个对象的指针。此功能可用于指向成员对象,同时拥有它们所属的对象。存储的指针是 get()
、解引用和比较运算符访问的指针。当使用计数达到零时,托管指针是传递给删除器的指针。
shared_ptr
也可以不拥有任何对象,在这种情况下它被称为空(如果使用别名构造函数创建,空的 shared_ptr
可能有一个非 null
存储指针)。
shared_ptr
的所有特化都满足 CopyConstructible
、CopyAssignable
和 LessThanComparable
的要求,并且可以上下文转换为 bool
。
所有成员函数(包括拷贝构造函数和拷贝赋值)都可以由多个线程在 shared_ptr
的不同实例上调用,而无需额外同步,即使这些实例是拷贝并且共享同一对象的所有权。如果多个执行线程在不同步的情况下访问同一 shared_ptr
实例,并且其中任何一个访问使用了 shared_ptr
的非 const 成员函数,则会发生数据竞争;可以使用原子函数的 shared_ptr
重载来防止数据竞争。
成员类型
pub | element_type | T (直到 C++17) std::remove_extent_t<T> (自 C++17 起) |
pub | weak_type (自 C++17 起) | std::weak_ptr<T> |
成员函数
pub | (构造函数) | 构造新的 shared_ptr (公共成员函数) |
pub | (析构函数) | 如果没有更多的 shared_ptr 链接到它,则销毁所拥有的对象 (公共成员函数) |
pub | operator= | 赋值 shared_ptr (公共成员函数) |
修改器
pub | reset | 替换托管对象 (公共成员函数) |
pub | swap | 交换托管对象 (公共成员函数) |
观察者
pub | get | 返回存储的指针 (公共成员函数) |
pub | operator* | 解引用存储的指针 (公共成员函数) |
pub | operator_at (C++17) | 提供对存储数组的索引访问 (公共成员函数) |
pub | use_count | 返回引用同一托管对象的 shared_ptr 对象的数量 (公共成员函数) |
pub | unique (直到 C++20) | 检查托管对象是否仅由当前 shared_ptr 实例管理 (公共成员函数) |
pub | operator bool | 检查存储的指针是否不为空 (公共成员函数) |
pub | owner_before | 提供基于所有者的 shared_ptr 排序 (公共成员函数) |
非成员函数
帮助类
pub | std::atomic<std::shared_ptr>(C++20) | 原子共享指针 (类模板特化) |
pub | std::hash<std::shared_ptr>(C++11) | 对 std::shared_ptr 的哈希支持 (类模板特化) |
推导指南 (自 C++17 起)
备注
对象的所有权只能通过将其值拷贝构造或拷贝赋值给另一个 shared_ptr
来与另一个 shared_ptr
共享。使用另一个 shared_ptr
拥有的原始底层指针构造新的 shared_ptr
会导致未定义行为。
std::shared_ptr
可以与不完整类型 T
一起使用。但是,从原始指针构造函数(template<class Y> shared_ptr(Y*)
)和 template<class Y> void reset(Y*)
成员函数只能与指向完整类型的指针一起调用(注意 std::unique_ptr
可以从指向不完整类型的原始指针构造)。
std::shared_ptr<T>
中的 T
可以是函数类型:在这种情况下,它管理函数指针,而不是对象指针。这有时用于在任何函数被引用时保持动态库或插件加载
void del(void(*)()) {}
void fun() {}
int main()
{
std::shared_ptr<void()> ee(fun, del);
(*ee)();
}
实现注意事项
在典型实现中,shared_ptr 只持有两个指针
- 存储的指针(由 get() 返回);
- 指向控制块的指针。
控制块是一个动态分配的对象,它包含
- 指向托管对象的指针或托管对象本身;
- 删除器(类型擦除);
- 分配器(类型擦除);
- 拥有托管对象的 shared_ptr 数量;
- 引用托管对象的 weak_ptr 数量。
当通过调用 std::make_shared
或 std::allocate_shared
创建 shared_ptr
时,控制块和托管对象的内存通过一次分配创建。托管对象在控制块的数据成员中就地构造。当通过 shared_ptr
构造函数之一创建 shared_ptr
时,托管对象和控制块必须单独分配。在这种情况下,控制块存储指向托管对象的指针。
shared_ptr
直接持有的指针是由 get()
返回的指针,而控制块持有的指针/对象是当共享所有者数量达到零时将被删除的指针/对象。这些指针不一定相等。
shared_ptr
的析构函数会递减控制块的共享所有者数量。如果该计数器达到零,控制块会调用托管对象的析构函数。控制块直到 std::weak_ptr
计数器也达到零才解除自身分配。
在现有实现中,如果有指向相同控制块的共享指针,则弱指针的数量会增加 ([1], [2])。
为了满足线程安全要求,引用计数器通常使用类似于 std::atomic::fetch_add
和 std::memory_order_relaxed
的方式递增(递减需要更强的顺序以安全销毁控制块)。
示例 1
#include <chrono>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
using namespace std::chrono_literals;
struct Base
{
Base() { std::cout << "Base::Base()\n"; }
// Note: non-virtual destructor is OK here
~Base() { std::cout << "Base::~Base()\n"; }
};
struct Derived: public Base
{
Derived() { std::cout << "Derived::Derived()\n"; }
~Derived() { std::cout << "Derived::~Derived()\n"; }
};
void print(auto rem, std::shared_ptr<Base> const& sp)
{
std::cout << rem << "\n\tget() = " << sp.get()
<< ", use_count() = " << sp.use_count() << '\n';
}
void thr(std::shared_ptr<Base> p)
{
std::this_thread::sleep_for(987ms);
std::shared_ptr<Base> lp = p; // thread-safe, even though the
// shared use_count is incremented
{
static std::mutex io_mutex;
std::lock_guard<std::mutex> lk(io_mutex);
print("Local pointer in a thread:", lp);
}
}
int main()
{
std::shared_ptr<Base> p = std::make_shared<Derived>();
print("Created a shared Derived (as a pointer to Base)", p);
std::thread t1{thr, p}, t2{thr, p}, t3{thr, p};
p.reset(); // release ownership from main
print("Shared ownership between 3 threads and released ownership from main:", p);
t1.join(); t2.join(); t3.join();
std::cout << "All threads completed, the last one deleted Derived.\n";
}
Base::Base()
Derived::Derived()
Created a shared Derived (as a pointer to Base)
get() = 0x118ac30, use_count() = 1
Shared ownership between 3 threads and released ownership from main:
get() = 0, use_count() = 0
Local pointer in a thread:
get() = 0x118ac30, use_count() = 5
Local pointer in a thread:
get() = 0x118ac30, use_count() = 4
Local pointer in a thread:
get() = 0x118ac30, use_count() = 2
Derived::~Derived()
Base::~Base()
All threads completed, the last one deleted Derived.
示例 2
#include <iostream>
#include <memory>
struct MyObj
{
MyObj() { std::cout << "MyObj constructed\n"; }
~MyObj() { std::cout << "MyObj destructed\n"; }
};
struct Container : std::enable_shared_from_this<Container> // note: public inheritance
{
std::shared_ptr<MyObj> memberObj;
void CreateMember() { memberObj = std::make_shared<MyObj>(); }
std::shared_ptr<MyObj> GetAsMyObj()
{
// Use an alias shared ptr for member
return std::shared_ptr<MyObj>(shared_from_this(), memberObj.get());
}
};
#define COUT(str) std::cout << '\n' << str << '\n'
#define DEMO(...) std::cout << #__VA_ARGS__ << " = " << __VA_ARGS__ << '\n'
int main()
{
COUT( "Creating shared container" );
std::shared_ptr<Container> cont = std::make_shared<Container>();
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
COUT( "Creating member" );
cont->CreateMember();
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
COUT( "Creating another shared container" );
std::shared_ptr<Container> cont2 = cont;
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
DEMO( cont2.use_count() );
DEMO( cont2->memberObj.use_count() );
COUT( "GetAsMyObj" );
std::shared_ptr<MyObj> myobj1 = cont->GetAsMyObj();
DEMO( myobj1.use_count() );
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
DEMO( cont2.use_count() );
DEMO( cont2->memberObj.use_count() );
COUT( "Copying alias obj" );
std::shared_ptr<MyObj> myobj2 = myobj1;
DEMO( myobj1.use_count() );
DEMO( myobj2.use_count() );
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
DEMO( cont2.use_count() );
DEMO( cont2->memberObj.use_count() );
COUT( "Resetting cont2" );
cont2.reset();
DEMO( myobj1.use_count() );
DEMO( myobj2.use_count() );
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
COUT( "Resetting myobj2" );
myobj2.reset();
DEMO( myobj1.use_count() );
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
COUT( "Resetting cont" );
cont.reset();
DEMO( myobj1.use_count() );
DEMO( cont.use_count() );
}
Creating shared container
cont.use_count() = 1
cont->memberObj.use_count() = 0
MyObj constructed
Creating member
cont.use_count() = 1
cont->memberObj.use_count() = 1
Creating another shared container
cont.use_count() = 2
cont->memberObj.use_count() = 1
cont2.use_count() = 2
cont2->memberObj.use_count() = 1
GetAsMyObj
myobj1.use_count() = 3
cont.use_count() = 3
cont->memberObj.use_count() = 1
cont2.use_count() = 3
cont2->memberObj.use_count() = 1
Copying alias obj
myobj1.use_count() = 4
myobj2.use_count() = 4
cont.use_count() = 4
cont->memberObj.use_count() = 1
cont2.use_count() = 4
cont2->memberObj.use_count() = 1
Resetting cont2
myobj1.use_count() = 3
myobj2.use_count() = 3
cont.use_count() = 3
cont->memberObj.use_count() = 1
Resetting myobj2
myobj1.use_count() = 2
cont.use_count() = 2
cont->memberObj.use_count() = 1
Resetting cont
myobj1.use_count() = 1
cont.use_count() = 0
MyObj destructed