1.std::shared_ptr使用场景
#include <iostream>
#include <memory>
using namespace std;
shared_ptr<int> create0(int value) {
return make_shared<int>(value); // 返回一个shared_ptr
}
//void myfunc(int value) {
shared_ptr<int> myfunc(int value) {
shared_ptr<int> ptmp = create0(10);
//return; // 离开作用域后,ptemp会被自动释放,它所指向的内存也会自动释放
return ptmp; // 系统是根据ptmp这个局部变量来产生一个临时的shared_ptr对象往回返
}
int main() {
//一:std::shared_ptr使用场景
myfunc(12);
// 如果这块不用shared_ptr变量来接收myfunc返回的结果,那么从myfunc返回的shared_ptr
// 就会被销毁,所指向的对象也会被销毁
auto p11 = myfunc(12);
// 我们用了一个变量来接myfunc的返回值,那么myfunc返回的shared_ptr
// 就不会被销毁,它所指向的对象也不会被销毁
return 0;
}
二、std::shared_ptr使用陷阱分析:一旦用错,也是致命的
2.1慎用裸指针
避免使用匿名的临时shared_ptr<T>对象(Effective C++中写道)
#include <iostream>
#include <memory>
using namespace std;
void proc(shared_ptr<int> value) {
}
int main() {
int*p = new int(100); //裸指针
//proc(p); // 语法错, int*p 不能转换成shared_ptr<int>
//error: could not convert ‘p’ from ‘int*’ to ‘std::shared_ptr<int>’
#if 0
shared_ptr<int> p2(p);
proc(p2);
#endif
proc(shared_ptr<int>(p)); // 参数是个临时的shared_ptr,用一个裸指针显示的构造
*p = 45; // 潜在的不可预料的问题;因为p指向的内存已经被释放了
return 0;
}
2.2慎用get()返回的指针
返回只能指针指向的对象所对应的裸指针(有些函数接口可能只能使用裸指针)
get返回的指针不能delete,否则会异常
#include <iostream>
#include <memory>
using namespace std;
int main() {
#if 0
shared_ptr<int> myp(new int(100));
int *p = myp.get();
delete p; // 不可以删除,会导致异常
#endif
// 不能将其他智能指针绑到get返回的指针上
shared_ptr<int> myp(new int(100));
int *p = myp.get(); // 这个指针千万不能随意释放,否则myp就没办法正常管理该指针了
{
shared_ptr<int> myp2(p);// 错误,现在myp和myp2引用计数都为1,但一旦跳出这个程序块,p指针指向的内存被释放
// shared_ptr<int> myp2;
// myp2 = shared_ptr<int>(myp); // 错误
// shared_ptr<int> myp2(myp); // 正确
}
// 离开上边myp2的范围,导致myp指向的内存被释放了
*myp = 65; //该内存已经被释放,这样复制会导致不可预料的后果
return 0;
}
结论:永远不要用get得到的指针来初始化另一个智能指针或者给另改一个智能赋值
2.3不要把类对象指针(this)作为shared_ptr返回,改用enable_shared_from_this
#include <iostream>
#include <memory>
using namespace std;
class CT: public enable_shared_from_this<CT> {
public:
shared_ptr<CT> get_self() {
// return shared_ptr<CT>(this); // 用裸指针初始化多个shared_ptr的感觉
return shared_from_this(); // 这个就是enable_shared_from_this类中的方法
}
};
int main() {
shared_ptr<CT> pct1(new CT);
// shared_ptr<CT> pct2 = pct1; // 这两个强饮用;
shared_ptr<CT> pct2 = pct1->get_self();
cout << pct2.use_count() << endl; // 2
// get_self函数如果使用裸指针,会出现问题
// 用到c++标准库里边的类模板:enable_shared_from_this:
// 现在,在外面创建CT对象的智能指针以及通过CT对象返回的this智能指针都是安全的
// 这个enable_shared_from_this中有一个弱指针weak_ptr,这个弱指针能够监视this
// 在我们调用shared_from_this()这个方法时,这个方法内部实际上是调用weak_ptr的lock方法
// 大家都知道lock()会让shared_ptr指针计数+1,同时返回这个shared_ptr,这就是工作原理
return 0;
}
2.4避免循环引用:能够导致内存泄露
妖异的代码
#include <iostream>
#include <memory>
using namespace std;
class CB;
class CA {
public:
shared_ptr<CB> m_pbs;
~CA() {
int test;
test =1;
cout << "~CA()" << endl;
}
};
class CB {
public:
//shared_ptr<CA> m_pas;
weak_ptr<CA> m_pas; // 把这里变成弱引用
~CB() {
int test;
test =1;
cout << "~CB()" << endl;
}
};
int main() {
#if 0
shared_ptr<CA> pca(new CA);
shared_ptr<CB> pcb(new CB);
pca->m_pbs = pcb; // 等价于指向CB对象的有两个强引用
pcb->m_pas = pca; // 妖异,等价于指向CA对象的有两个强引用
#endif
shared_ptr<CA> pca(new CA);
shared_ptr<CB> pcb(new CB);
pca->m_pbs = pcb; // 等价于指向CB对象的有两个强引用
pcb->m_pas = pca; // 因为m_pas是弱引用,所以这里指向CA的对象只有一个强引用
// 离开作用域之后,pca引用技术从1就变成0会释放CA对象(CA的析构函数被执行)
// CA的析构函数被执行了(表示对象即将被释放),
// 导致CA内的m_pbs引用技术会减1,也就是指向CB对象的引用技术-1
// 超出pcb作用域时指向CB的计数也会-1,
// 最终,会有一个时刻,指向CB对象的强引用计数从1较少到0,导致CB对象被释放
return 0;
}
三、性能说明
3.1尺寸问题
#include <iostream>
#include <memory>
using namespace std;
int main() {
char *p;
int lenp = sizeof(p); // 4字节
cout << lenp << endl;
shared_ptr<string> p1;
int ilensp = sizeof(p1);
cout << ilensp << endl; // 8字节,包含两个裸指针
return 0;
}
shared_ptr的尺寸是裸指针的2倍;weak_ptr尺寸裸指针的2倍
a)第一个裸指针指向的是这个智能指针所指向的对象
b)第二个裸指针指向一个很大的数据结构(控制块),这个控制块里边有啥:
b.1)所指对象的强引用计数:shared_ptr
b.2)所指对象的弱引用计数:weak_ptr
b.3)其他数据,比如删除器指针,内存分配器
这个控制块是由第一个指向某个对象的shared_ptr创建的
控制块创建时机:
a)make_shared:分配并初始化一个对象,返回指向对象的shared_ptr,所以,这个make_shared它总是能够创建一个控制块
shared_ptr<int> p2 = make_shared<int>(100);
b)用裸指针来创建一个shared_ptr对象时
int*p = new int();
shared_ptr<int> p1(pi);
shared_ptr<int> p2(pi); // 不允许,否则会产生多个控制块,也就是多个引用计数(每个都是1);析构时析构多次,导致异常
3.2.移动语义
shared_ptr<int> p1(new int(100));
shared_ptr<int> p2(std::move(p1)); // 移动语义,移动构造一个新的智能指针p2
// p1就不再指向该对象(变成空),引用计数依旧是1
shared_ptr<int> p3;
p3 = std::move(p2); // 移动赋值,p2指向空, p3指向该对象,整个对象的引用计数仍旧为1
移动肯定比复制快;复制你要增加引用技术,移动不需要,
移动构造函数快过复制构造函数,移动赋值运算符快过拷贝赋值运算符
4.补充说明和使用建议
a)掌握了绝大部分shared_ptr用法;小部分没讲解,靠大家摸索
分配器,解决了内存分配问题
shared_ptr<int> p((new int), mydeleted(), myallocator<int>())
b)谨慎使用
new shared_ptr<int>, memcpy() 奇怪用法,大家不要轻易尝试
c)优先使用make_shared(),不能让自己定义自己的删除器
shared_ptr<string> ps1(new string("I Love China!")); // 分配两次
auto ps2 = make_shared_<string>("I love Chind!"); // 分配1次内存
5.多线程下shared_ptr
(1)一个 shared_ptr 对象实体可被多个线程同时读取;
(2)两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;
(3) 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。
为什么多线程读写 shared_ptr 要加锁?_陈硕的博客-CSDN博客_多线程读写
(4)多线程的情况下,shared_ptr的use_count成员技数是不准确的。业务场景:如果使用use_count是否等于1,判断是否有其他线程在使用。是不正确的。
常用使用包括
- 与 0 比较。若
use_count返回零,则智能指针为空且不管理对象(无论被存储指针是否为空)。多线程环境下,这不隐含被管理对象的析构函数已完成。 - 与 1 比较。若
use_count返回 1 ,则无其他拥有者。(被弃用成员函数 unique() 为此使用情况提供。)多线程环境中,这不隐含对象可以安全修改,因为先前拥有者对被管理对象的访问可能未完成,而因为新的共享拥有者可以同时引入,例如用 std::weak_ptr::lock 。
std::memory_order - cppreference.com
std::shared_ptr<T>::use_count - cppreference.com
深入RPC - Atomic instructions - 《BRPC v0.9.7 开发手册》 - 书栈网 · BookStack
多线程安全引用计数读写:https://github.com/apache/incubator-brpc/blob/master/src/butil/memory/scoped_ptr.h






#include <iostream>
#include <cstddef>
#include <utility>
#include <cassert>
#include <new> // placement new
// ------------------------ SharedPtr 实现 ------------------------
template <typename T>
class SharedPtr {
public:
// ---------- 构造 ----------
SharedPtr() noexcept : ptr_(nullptr), ctrl_(nullptr) {}
explicit SharedPtr(T* p) : ptr_(p) {
if (p) {
ctrl_ = new ControlBlock();
ctrl_->shared_count = 1;
ctrl_->alloc_base = nullptr;
ctrl_->deleter = nullptr;
} else {
ctrl_ = nullptr;
}
}
SharedPtr(const SharedPtr& other) noexcept
: ptr_(other.ptr_), ctrl_(other.ctrl_) {
if (ctrl_) {
++ctrl_->shared_count;
}
}
SharedPtr(SharedPtr&& other) noexcept
: ptr_(other.ptr_), ctrl_(other.ctrl_) {
other.ptr_ = nullptr;
other.ctrl_ = nullptr;
}
~SharedPtr() {
release();
}
SharedPtr& operator=(const SharedPtr& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
ctrl_ = other.ctrl_;
if (ctrl_) {
++ctrl_->shared_count;
}
}
return *this;
}
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
ctrl_ = other.ctrl_;
other.ptr_ = nullptr;
other.ctrl_ = nullptr;
}
return *this;
}
T* get() const noexcept { return ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
std::size_t use_count() const noexcept {
return ctrl_ ? ctrl_->shared_count : 0;
}
explicit operator bool() const noexcept { return ptr_ != nullptr; }
void reset() noexcept {
release();
ptr_ = nullptr;
ctrl_ = nullptr;
}
void reset(T* p) {
release();
ptr_ = p;
if (p) {
ctrl_ = new ControlBlock();
ctrl_->shared_count = 1;
ctrl_->alloc_base = nullptr;
ctrl_->deleter = nullptr;
} else {
ctrl_ = nullptr;
}
}
private:
struct ControlBlock {
std::size_t shared_count;
void* alloc_base; // 合并分配时的基址,普通分配时为 nullptr
void (*deleter)(void*); // 自定义删除器
};
T* ptr_;
ControlBlock* ctrl_;
void release() {
if (ctrl_) {
--ctrl_->shared_count;
if (ctrl_->shared_count == 0) {
if (ctrl_->deleter) {
// 合并分配路径:手动析构对象,再通过删除器释放整块内存
ptr_->~T();
ctrl_->deleter(ctrl_->alloc_base);
} else {
// 普通分配路径:分别 delete
delete ptr_;
delete ctrl_;
}
}
}
}
// 为 MakeShared 提供的私有构造函数
SharedPtr(T* p, ControlBlock* ctrl) noexcept
: ptr_(p), ctrl_(ctrl) {}
template <typename U, typename... Args>
friend SharedPtr<U> MakeShared(Args&&... args);
};
// ------------------------ MakeShared 工厂 ------------------------
template <typename T, typename... Args>
SharedPtr<T> MakeShared(Args&&... args) {
// 一块内存同时容纳控制块和对象
struct Block {
typename SharedPtr<T>::ControlBlock ctrl;
T obj;
};
// 1. 分配原始内存,不调用任何构造函数
void* raw = ::operator new(sizeof(Block));
// 2. 先在控制块位置 placement new(控制块是 trivial 可简单构造)
auto* block = static_cast<Block*>(raw);
::new (&block->ctrl) typename SharedPtr<T>::ControlBlock();
// 3. 尝试构造对象,如果失败则清理控制块并释放内存
try {
::new (&block->obj) T(std::forward<Args>(args)...);
} catch (...) {
// 对象构造失败,析构已构造的控制块,释放内存
block->ctrl.~ControlBlock();
::operator delete(raw);
throw;
}
// 4. 设置控制块参数
block->ctrl.shared_count = 1;
block->ctrl.alloc_base = block; // 整个 Block 的基址
block->ctrl.deleter = [](void* p) {
Block* realBlock = static_cast<Block*>(p);
// 注意:对象已经由 release() 中手动析构,这里只需释放内存
::operator delete(realBlock);
};
return SharedPtr<T>(&block->obj, &block->ctrl);
}
// ------------------------ 测试辅助 ------------------------
struct TestObj {
int value;
TestObj(int v) : value(v) {
std::cout << " TestObj(" << value << ") 构造\n";
}
~TestObj() {
std::cout << " TestObj(" << value << ") 析构\n";
}
void say() const {
std::cout << " Hello from TestObj(" << value << ")\n";
}
};
// ------------------------ 测试用例 ------------------------
int main() {
std::cout << "========== 1. 默认构造 ==========\n";
SharedPtr<int> sp0;
assert(sp0.use_count() == 0);
assert(!sp0);
std::cout << "\n========== 2. 从原始指针构造 ==========\n";
SharedPtr<TestObj> sp1(new TestObj(10));
std::cout << "use_count = " << sp1.use_count() << '\n';
sp1->say();
std::cout << "\n========== 3. 拷贝构造 ==========\n";
SharedPtr<TestObj> sp2(sp1);
std::cout << "sp1.use_count = " << sp1.use_count()
<< ", sp2.use_count = " << sp2.use_count() << '\n';
sp2->say();
std::cout << "\n========== 4. 拷贝赋值 ==========\n";
SharedPtr<TestObj> sp3(new TestObj(20));
std::cout << "sp3.use_count = " << sp3.use_count() << '\n';
sp3 = sp1;
std::cout << "sp1.use_count = " << sp1.use_count()
<< ", sp3.use_count = " << sp3.use_count() << '\n';
std::cout << "\n========== 5. 移动构造 ==========\n";
SharedPtr<TestObj> sp4(std::move(sp3));
std::cout << "sp4.use_count = " << sp4.use_count()
<< ", sp3.use_count = " << sp3.use_count()
<< ", sp1.use_count = " << sp1.use_count() << '\n';
assert(!sp3);
std::cout << "\n========== 6. 移动赋值 ==========\n";
SharedPtr<TestObj> sp5(new TestObj(30));
std::cout << "移动前: sp5.use_count = " << sp5.use_count() << '\n';
sp5 = std::move(sp4);
std::cout << "移动后: sp5.use_count = " << sp5.use_count()
<< ", sp4.use_count = " << sp4.use_count()
<< ", sp1.use_count = " << sp1.use_count() << '\n';
assert(!sp4);
std::cout << "\n========== 7. 自赋值 ==========\n";
sp1 = sp1;
std::cout << "自赋值后 use_count = " << sp1.use_count() << '\n';
std::cout << "\n========== 8. MakeShared (一次分配) ==========\n";
auto sp6 = MakeShared<TestObj>(40);
std::cout << "sp6.use_count = " << sp6.use_count() << '\n';
sp6->say();
std::cout << "\n========== 9. 作用域结束,自动析构 ==========\n";
{
SharedPtr<TestObj> sp7(sp6);
std::cout << "内层: sp6.use_count = " << sp6.use_count() << '\n';
}
std::cout << "离开作用域后: sp6.use_count = " << sp6.use_count() << '\n';
std::cout << "\n========== 10. 重置 ==========\n";
sp6.reset();
assert(sp6.use_count() == 0);
std::cout << "sp6 重置后,sp1 仍持有: use_count = " << sp1.use_count() << '\n';
std::cout << "\n========== 所有 SharedPtr 将离开作用域 ==========\n";
return 0;
}
本文深入解析C++中std::shared_ptr的使用场景、常见陷阱及性能特性,包括裸指针初始化、get()方法使用、循环引用避免、尺寸与移动语义,以及多线程下的注意事项。


6373

被折叠的 条评论
为什么被折叠?



