C++11的智能指针
为什么需要智能指针:
在传统的C++中,程序员一般都使用new和delete来手动管理内存,这就带来了内存泄漏
① 忘记delete:在分配内存后,由于复杂的程序逻辑或者异常而忘记delete,那么这块内存在程序结束前都无法被释放
② 异常:如果程序在new和delete之间抛出了异常,delete语句可能不会执行
其核心思想:就是将一个原生指针封装在一个智能指针对象当中,而当该对象离开作用域时,它的析构函数会自动调用delete来释放其管理的内存。也被称为RAII(资源获取即初始化)资源在对象构造时获取,在对象析构(也就是相当于离开作用域)时自动释放,实现自动管理内存
一、std::unique_ptr(独占所有权智能指针)
在同一时间,一个对象只能由一个unique_ptr所拥有,它不能被复制(也就是不允许拷贝构造和拷贝赋值),但是支持移动(移动构造和移动赋值)
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : m_data(value) {
std::cout << "MyClass constructed with " << m_data << std::endl;
}
~MyClass() {
std::cout << "MyClass destroyed with " << m_data << std::endl;
}
void print() const {
std::cout << "data: " << m_data << std::endl;
}
private:
int m_data;
};
int main() {
// 创建一个 unique_ptr
std::unique_ptr<MyClass> p1(new MyClass(10));
std::unique_ptr<MyClass> p3;
p3.reset(new MyClass(20));
std::unique_ptr<MyClass> p4 = std::make_unique<MyClass>(30);
p1->print();
// 编译错误!无法复制 unique_ptr
// std::unique_ptr<MyClass> p2 = p1;
// 但是支持移动
std::unique_ptr<MyClass> p2 = std::move(p1);
if (!p1) {
std::cout << "p1 is now null after move." << std::endl;
}
if (p2) {
p2->print(); // p2 现在拥有对象
}
// 当 p2 离开作用域,自动调用析构函数
return 0;
}
MyClass constructed with 10
MyClass constructed with 20
MyClass constructed with 30
data: 10
p1 is now null after move.
data: 10
MyClass destroyed with 10
MyClass destroyed with 30
MyClass destroyed with 20
二、std::shared_ptr(共享所有权智能指针)
一个对象可以有多个 shared_ptr 指向它。内部使用引用计数机制来跟踪有多少个 shared_ptr 共享同一个对象。当最后一个 shared_ptr 被销毁或重置时,对象才会被销毁。
如果想查看有多少个共享智能指针同时管理这块内存,就可以使用一个成员函数 use_count
如果一个智能指针被初始化了一块有效的内存,那这块内存的引用计数加一,如果没有初始化或者被初始化为nullptr,引用计数不变
初始化:
#include <iostream>
#include <memory>
int main() {
// 一、通过构造函数初始化
std::shared_ptr<int> p0;
std::cout << "p0管理的内存的引用计数为:" << p0.use_count() << std::endl;
std::shared_ptr<int> p1(new int(9));
std::cout << "p1管理的内存的引用计数为:" << p1.use_count() << std::endl;
// 调用拷贝构造
std::shared_ptr<int> p2(p1);
std::cout << "p2管理的内存的引用计数为:" << p2.use_count() << std::endl;
// 调用移动构造
std::shared_ptr<int> p3(std::move(p1));
std::cout << "p3管理的内存的引用计数为:" << p3.use_count() << std::endl;
// 二、通过std::make_shared初始化
std::shared_ptr<int> p4 = std::make_shared<int>(9);
std::cout << "p4管理的内存的引用计数为:" << p4.use_count() << std::endl;
// 三、通过reset初始化
std::shared_ptr<int> p5;
p5.reset(new int(10));
std::cout << "p5管理的内存的引用计数为:" << p5.use_count() << std::endl;
return 0;
}
注:对于一个还没有初始化的智能指针,可以用reset初始化,但是当智能指针中有值时,调用reset后,其引用计数会减一
p0管理的内存的引用计数为:0
p1管理的内存的引用计数为:1
p2管理的内存的引用计数为:2
p3管理的内存的引用计数为:2
p4管理的内存的引用计数为:1
p5管理的内存的引用计数为:1
三、std::weak_ptr(弱引用智能指针,与std::shared_ptr配合使用)
weak_ptr可以看作shared_ptr的助手,它不管理shared_ptr内部的指针,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数。其主要作用是监视shared_ptr中管理的资源是否存在
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p0(new int(9));
std::weak_ptr<int> w_p1; // 空的weak_ptr对象
std::weak_ptr<int> w_p2(w_p1); // 通过空weak_ptr对象构造另一个空对象
std::weak_ptr<int> w_p3(p0); // 通过p0构造了可用的weak_ptr实例对象
std::weak_ptr<int> w_p4;
w_p4 = p0;
std::cout << "p0 --> use_count " << p0.use_count() << std::endl;
std::cout << "w_p1 --> use_count " << w_p1.use_count() << std::endl;
std::cout << "w_p2 --> use_count " << w_p2.use_count() << std::endl;
std::cout << "w_p3 --> use_count " << w_p3.use_count() << std::endl;
std::cout << "w_p4 ---> use_count " << w_p4.use_count() << std::endl;
return 0;
}
p0 --> use_count 1
w_p1 --> use_count 0
w_p2 --> use_count 0
w_p3 --> use_count 1
w_p4 ---> use_count 1
通过输出结果可以清楚,weak_ptr对象w_p3和w_p4监测的资源是同一个,但它的计数没有变化,证明了weak_ptr只是监测shared_ptr中资源,并不管理资源
其他常用函数
如expired() 和 lock() 前者来判断所监测的资源是否释放,后者则是获取管理所监测资源的shared_ptr对象
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p0(new int(9));
std::weak_ptr<int> w_p1(p0);
// bool expired()
if(w_p1.expired()) {
std::cout << "资源已被释放" << std::endl;
}
else {
std::cout << "资源未被释放" << std::endl;
}
p0.reset();
if(w_p1.expired()) {
std::cout << "资源已被释放" << std::endl;
}
else {
std::cout << "资源未被释放" << std::endl;
}
std::shared_ptr<int> p1, p2;
std::weak_ptr<int> w_p2;
p1 = std::make_shared<int>(10);
w_p2 = p1;
p2 = w_p2.lock();
std::cout << "use_count " << w_p2.use_count() << std::endl;
p1.reset();
std::cout << "use_count " << w_p2.use_count() << std::endl;
p1 = w_p2.lock();
std::cout << "use_count " << w_p2.use_count() << std::endl;
return 0;
}
资源未被释放
资源已被释放
use_count 2
use_count 1
use_count 2
解决循环引用问题
#include <iostream>
#include <memory>
class A;
class B;
class A {
public:
std::shared_ptr<B> p1;
~A() {
std::cout << "A被销毁" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> p2;
~B() {
std::cout << "B被销毁" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->p1 = b;
b->p2 = a;
// 主函数结束,a和b相互持有,导致引用技术无法归零
// 析构函数不会执行,内存泄漏
return 0;
}
解决方法
#include <iostream>
#include <memory>
class A;
class B;
class A {
public:
std::shared_ptr<B> p1;
~A() {
std::cout << "A被销毁" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> w_p2;
~B() {
std::cout << "B被销毁" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->p1 = b;
b->w_p2 = a;
return 0;
}
主函数结束时,a释放,其引用计数减一,为0,A销毁
A销毁后,其持有的p1释放,b引用计数减一
b释放,b引用计数再减一,变为0,B被销毁
A被销毁
B被销毁
小结
|
特性 |
|
|
|
|
所有权 |
独占 |
共享 |
无所有权(弱引用) |
|
复制语义 |
禁止复制,允许移动 |
允许复制和移动 |
允许复制和移动 |
|
使用场景 |
明确独占资源的场景 |
需要共享所有权的场景 |
解决 循环引用 |
更多推荐



所有评论(0)