文章目录
unique_ptr
所有权转移
unique_ptr不能被拷贝和用于赋值,因为unique_ptr删掉了这两个函数
但是底层源码重载了传右值的拷贝构造
所以可以通过std::move来通过转移所有权
unique_ptr<Data> p6(new Data());
//不可复制构造和赋值复制
//unique_ptr<Data>p7 = p6; 错误
//p6释放所有权 转移到p7
unique_ptr<Data>p7 = move(p6);
unique_ptr<Data>p8(new Data());
p7 = move(p8);//重新移动赋值,原有的p6会被释放掉
//重置空间,原空间清理
p7.reset(new Data());
所有权释放
注意!当unique_ptr释放所有权以后智能指针就不会再管理这块空间,需要自己手动释放空间!
//释放所有权
unique_ptr<Data>p9(new Data());
auto ptr9 = p9.release();//注意,release释放所有权以后要自己清理空间
delete ptr9;//!!!!!!
make_unique 和直接(new)unique_ptr
区别一 :需要一次性处理多个资源分配的地方make_unique比unique_ptr更安全
void process_data(
std::unique_ptr<Data> p1,
std::unique_ptr<Data> p2
);
process_data(
std::unique_ptr<Data>(new Data("A")), // 分配资源 A
std::unique_ptr<Data>(new Data("B")) // 分配资源 B
);
编译器在构造函数参数时,执行顺序是不确定的。可能的执行顺序例如:
- new Data(“A”) → 成功,得到一个裸指针 A
- new Data(“B”) → 成功,得到一个裸指针 B*
- 构造unique_ptr 接管 A*
- 构造 unique_ptr 接管 B*
如果中间发生异常:
- new Data(“A”) → 成功,得到 A*
- new Data(“B”) → 抛出异常(例如内存不足) 此时 A* 尚未被 unique_ptr 接管! 异常被抛出后,裸指针 A* 无法被自动释放 → 内存泄漏。
如果选用make_unique
process_data(
std::make_unique<Data>("A"), // 直接构造并接管资源 A
std::make_unique<Data>("B") // 直接构造并接管资源 B
);
此时每一步的执行:
- make_unique(“A”) → 立即构造对象并封装到 unique_ptr,无裸指针暴露
- make_unique(“B”) → 同上 如果 make_unique(“B”) 抛出异常:
make_unique(“A”) 已经返回的 unique_ptr 会正常析构 → 资源 A 被自动释放没有泄漏!
区别二 :直接new支持自定义删除器
new支持在构造 unique_ptr 时指定自定义删除器
std::unique_ptr<MyClass, Deleter> p(new MyClass, custom_deleter);
但是make_unique不支持,只能使用默认的 delete 操作符。
shared_ptr
shared_ptr智能指针指向同一个对象的不同成员
sc2和sc3 分别 指向sc1的index1成员和index2成员,使sc1的引用计数+2
class Data
{
public:
Data() {
cout<< "Begin Data" << endl;
}
~Data() { cout<< "End Data" << endl; }
int index1 = 0;
int index2 = 0;
};
{
shared_ptr<Data>sc1(new Data);
//打印引用计数 = 1
cout << "sc1.use_count() = " << sc1.use_count() << endl;
shared_ptr<int>sc2(sc1,&sc1->index1);//引用计数+1
shared_ptr<int>sc3(sc1, &sc1->index2);//引用计数+1
//打印引用计数 = 3
cout << "sc1.use_count() = " << sc1.use_count() << endl;
}
循环引用问题
当两个或多个对象通过 shared_ptr 互相持有对方时,它们的引用计数永远不会归零,导致内存无法释放。
class A
{
public:
A() { cout << "Create A" << endl; }
~A() { cout << " Drop A " << endl; }
void Do()
{
cout << "Do b2.use_count() = " << b2.use_count() << endl;
auto b = b2.lock(); //复制一个shared_ptr 引用计数加一
cout << "Do b2.use_count() = " << b2.use_count() << endl;
}
shared_ptr<B> b1;//强智能指针
weak_ptr<B> b2; //弱智能指针
};
class B
{
public:
B() { cout << "Create B" << endl; }
~B(){ cout << " Drop B " << endl; }
shared_ptr<A> a1;//强智能指针
weak_ptr<A> a2; //弱智能指针
};
{
auto a = make_shared<A>();//a引用计数+1
auto b = make_shared<B>();//b引用计数+1
a->b1 = b;//b引用计数+1
//出作用域前,引用计数为2
cout << "a->b1 = b;b.use_count()=" << b.use_count() << endl;
b->a1 = a;//a引用计数+1
//出作用域前,引用计数为2
cout << "b->a1 = a;a.use_count()=" << a.use_count() << endl;
}
由于调用问题,导致出作用域以后a和b的引用计数都还是1,所以空间没有被释放
改成使用weak_ptr
{
auto a = make_shared<A>();
auto b = make_shared<B>();
a->b2 = b;//weak_ptr 引用计数不加一
a->Do();//Do函数里引用计数加一,出Do函数作用域减一
cout << "a->b2 = b;b.use_count()=" << b.use_count() << endl;
b->a2 = a;//引用计数不加一
cout << "b->a2 = a;a.use_count()=" << a.use_count() << endl;
}//不会产生循环引用问题
为什么weak_ptr能解决shared_ptr的循环引用问题
weak_ptr 本身不拥有资源所有权:
它只是观察 shared_ptr 管理的对象,不会增加引用计数。
所以a->b2 = b;这句不会增加b的引用计数
同理b->a2 = a;这句也不会增加a的引用计数
但是由于weak_ptr只是一个观察者,无法访问任何资源,仅“观察”资源,不拥有所有权如果想要访问资源该怎么办呢?
如同a->Do();里做的那样
只要原 shared_ptr(即 a)未释放资源,就可以通过 lock() 获取有效的 shared_ptr 并访问。
void Do()
{
cout << "Do b2.use_count() = " << b2.use_count() << endl;
auto b = b2.lock(); //返回一个shared_ptr 引用计数加一
//这里还可以做其他的访问shared_ptr资源的操作
cout << "Do b2.use_count() = " << b2.use_count() << endl;
}//出作用域加上的那个引用计数自动-1
通过weak_ptr.lock()来 返回 一个 shared_ptr 并将引用计数加一
(注意!只有当原shared_ptr对象还存在的时候才会返回shared_ptr,否者返回nullptr)
除了放函数里,还能放判断条件里
if (auto a_shared = b->a_weak.lock()) {
// 返回nullptr 说明shared_ptr被释放
//不会执行此处
} else {
//引用计数+1
std::cout << "A is already destroyed!" << std::endl; // 输出此句
}//引用计数-1
通过这种方式,weak_ptr 可以安全地观察资源,而不会导致循环引用或内存泄漏
make_shared 和 直接 new 一个shared_ptr的区别
区别一:make_shared资源分配更安全
原因和make_unique一样,这里就不再赘述了
区别二:直接new支持自定义删除器
区别三:内存分配方式不同
由于shared_ptr除了维护对象本身的内存以外还要维护一个控制块
- 强引用计数(use_count):记录有多少个 shared_ptr 共享对象
- 弱引用计数(weak_count):记录有多少个weak_ptr 观察对象
- 自定义删除器(如果存在)。
- 对象指针(指向实际分配的对象)。
make_shared 在底层会 一次性分配一块连续内存,既存储对象本身,也存储控制块。
但是new shared_ptr会有两次分配
std::shared_ptr<MyClass> p(new MyClass);
- 第一次分配:new MyClass 分配对象内存。
- 第二次分配:shared_ptr 构造函数内部为控制块分配内存。
为什么make_unique和直接new unique_ptr都是一次分配内存
因为unique_ptr 不需要维护引用计数,因此 没有控制块。无论通过 make_unique 还是直接 new,都只需分配对象内存
shared_ptr合并分配的缺点
对象和控制块内存绑定,即使所有 shared_ptr 销毁,若仍有 weak_ptr 存在,对象内存仍然需等待控制块释放(但析构函数会被及时调用)
shared_ptr控制块内存的延迟释放是内存泄漏吗?
由于make_shared 是一次性分配一块连续内存,同时存储 对象实例 和 控制块(包含引用计数、弱引用计数等)
所以当没有weak_ptr存在时
通过make_shared建立的shared_ptr:
- 对象析构函数立即被调用。
- 整块内存(对象 + 控制块)立即释放。
通过new建立的shared_ptr:
- 对象内存立即释放。
- 控制块内存也立即释放。
当有weak_ptr存在时
通过make_shared建立的shared_ptr:
- 对象析构函数被调用(资源清理)。
- 对象内存和控制块内存暂时保留(直到所有 weak_ptr 也被销毁)。
通过new建立的shared_ptr:
- 对象内存立即释放(仅保留控制块内存)。
由于make_shared对象和控制块内存是连续的,无法单独释放对象内存。所以必须等待所有 weak_ptr 销毁后,整块内存才能一起释放。
那这是内存泄漏吗?
不是! 内存泄漏的定义是:无法再访问且未释放的内存。
而在此时:
对象析构函数已被调用(资源已清理)。
内存仍被 weak_ptr 的控制块管理,虽然未释放,但程序仍能通过 weak_ptr 的机制感知到内存状态。
当所有 weak_ptr 销毁后,内存会被正确释放。
enable_shared_from_this 和 shared_from_this()
解决场景:
当一个对象已被 shared_ptr 管理时,若在成员函数中直接用 this 创建新的 shared_ptr,会导致 多个独立的引用计数 。当某一 shared_ptr 析构时,对象可能被提前销毁,引发悬空指针或双重释放。
问题场景示例1
class BackgroundWorker {
public:
void startWork() {
// 错误:裸指针 this 跨线程传递,主线程可能提前释放对象
std::thread([this]() {
doWork(); // 可能访问已销毁对象!
}).detach();
}
void doWork() { /* 自己实现的操作 */ }
};
int main() {
auto worker = std::make_shared<BackgroundWorker>();
worker->startWork();
worker.reset(); // 主线程释放对象
// 子线程仍在执行 doWork(),this 已失效!
}
enable_shared_from_this 内部保存一个 weak_ptr,指向与对象关联的 shared_ptr 控制块。通过 shared_from_this() 方法生成与已有 shared_ptr 共享所有权的 shared_ptr 确保引用计数一致。
解决示例
通过获取与 shared_ptr 共享所有权的智能指针,延长对象生命周期
class BackgroundWorker :
public std::enable_shared_from_this<BackgroundWorker> {
public:
void startWork() {
// 正确:通过 shared_from_this() 传递所有权
std::thread([self = shared_from_this()]() {
self->doWork(); // 安全:self 确保对象存活
}).detach();
}
// ...
};
问题场景示例2:
class TreeNode {
public:
void addChild() {
// 创建一个子节点 child,用 shared_ptr 管理
auto child = std::make_shared<TreeNode>();
// 错误:直接用 this 创建新的 shared_ptr,赋值给 child->parent
//child->parent引用计数也是 1
child->parent = std::shared_ptr<TreeNode>(this); // 危险!
// 将 child 添加到当前节点的 children 列表中
children.push_back(child);
}
std::shared_ptr<TreeNode> parent; // 父节点用 shared_ptr 管理
std::vector<std::shared_ptr<TreeNode>> children; // 子节点列表
};
int main() {
// 创建一个根节点 root,用 shared_ptr 管理
auto root = std::make_shared<TreeNode>();//root引用计数是 1
root->addChild(); // 调用 addChild,导致双重释放!
}
这段代码的关键问题出现在 child->parent = std::shared_ptr(this)
this 是当前 TreeNode 对象的裸指针(即 root 指向的对象)。
直接用 this 创建了一个新的 shared_ptr,赋值给 child->parent。这个新的 shared_ptr 与 root 是 完全独立 的,它不知道 root 的存在,因此它的引用计数 “也是 1”。
当 main 函数结束时,root 的引用计数减为 0,会释放它指向的 TreeNode 对象。
但此时 child->parent 也指向同一个 TreeNode 对象,且它的引用计数仍然是 1(如果 child 未被销毁)。当 child 析构时,child->parent 的引用计数减为 0,会再次尝试释放同一个 TreeNode 对象,导致** 双重释放**。
解决示例:
class TreeNode : public std::enable_shared_from_this<TreeNode> {
public:
void addChild() {
auto child = std::make_shared<TreeNode>();
// 通过 shared_from_this() 获取与 root 共享所有权的 shared_ptr
child->parent = shared_from_this(); // 引用计数 +1
children.push_back(child);
}
std::shared_ptr<TreeNode> parent;
std::vector<std::shared_ptr<TreeNode>> children;
};
int main() {
auto root = std::make_shared<TreeNode>();
root->addChild(); // 安全:root 的引用计数变为 2
}