Q## introduction
解决的问题
- 内存泄漏
- 共享所有权指针的传播与释放,如多线程使用同一个对象的析构问题
几个智能指针
auto_ptr(C++ 11 deprecated)- unique_ptr:独占对象的所有权(没有引用计数,故性能好)
- shared_ptr:共享对象的所有权(性能差)
- weak_ptr:配合shared_ptr解决循环引用问题
shared_ptr
内存模型
![在这里插入图
- shared_ptr内部包含两个指针,一个指向对象,另一个指向control block,control block中包含一个reference count,一个weak count和一些其他数据
- std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候,内存会自动被释放。
常用函数及基本用法
// 初始化
std::shared_ptr<int> p1(new int(1));
// 含义同上,但更推荐使用make_shared,因为它更高效
std::shared_ptr<int> p1 = make_shared<int>(1);
std::shared_ptr<int> p2 = p1;
std::shared_ptr<int> p3;
// reset():重置shared_ptr
// 如果reset不带参数,则将指针置空,如果指针唯一指向那个对象,还需要释放对象的内存
// 如果reset带参数,则将指针指向新对象,并将旧对象引用计数减一,或释放旧对象
p3.reset(new int(1));
if(p3) {
cout << "p3 is not null";
}
// use_count():返回shared_ptr的强引用计数
// unique():若use_count的值为1,返回true,否则为false
std::shared_ptr<int> p1;
p1.reset(new int(1));
std::shared_ptr<int> p2 = p1;
// 引用计数此时应该是2
cout << "p2.use_count() = " << p2.use_count()<< endl;
// get():返回对象的原始指针
// 不建议使用!
// 如果要使用请尽量不要保存get()的返回值;不要delete get()的返回值,会导致该对象被delete两次
shared_ptr<int> ptr = make_shared<int>(1);
int* p = ptr.get();
// 删除器:如果传入的对象没有析构函数,应该定义删除器并传入
// 如果管理的是动态数组,也应该指定删除器!!
void DeleteIntPtr(int *p) {
cout << "call DeleteIntPtr" << endl;
delete p;
}
int main() {
std::shared_ptr<int> p(new int(1), DeleteIntPtr);
//也可以写成lambda函数:
std::shared_ptr<int> p2(new int[10], [](int *p) {
delete[] p});
return 0;
}
注意事项
- 不要使用一个原始指针初始化多个shared_ptr
int* p = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); // error!
- 通过shared_from_this返回this指针
class A{
public:
shared_ptr<A> getself(){
return shared_ptr<A>(this);
}
~A(){
cout << "destruction" << endl;
}
};
int main() {
shared_ptr<A> ptr1(new A);
shared_ptr<A> ptr2 = ptr1->getself();
return 0;
}
将会调用两次析构函数。正确的用法应该是:
class A: public std::enable_shared_from_this<A>{
public:
shared_ptr<A> getself(){
return shared_from_this();
}
~A(){
cout << "destruction" << endl;
}
};
int main() {
shared_ptr<A> ptr1(new A);
shared_ptr<A> ptr2 = ptr1->getself();
return 0;
}
- 不要在函数实参中创建shared_ptr
- 避免循环引用,解决办法见weak_ptr
class B;
class C;
class B{
public:
shared_ptr<C> bc;
~B(){
cout << "B destruction" << endl;
}
};
class C{
public:
shared_ptr<B> cb;
~C(){
cout << "C destruction" << endl;
}
};
int main() {
// cicle reference
shared_ptr<B>ptr1(new B);
shared_ptr<C>ptr2(new C);
{
ptr1->bc = ptr2;
ptr2->cb = ptr1;
}
cout << "main" << endl;
return 0;
}
循环引用导致ptr1和ptr2的引用计数都为2,在离开作用域之后,二者的引用计数都减为一,并不会发生析构
unique_ptr
与shared_ptr的区别
- 不允许复制,只能通过move来移交所有权
unique_ptr<T> my_ptr(new T); // 正确
unique_ptr<T> my_other_ptr = std::move(my_ptr); // 正确
unique_ptr<T> ptr = my_ptr; // error
- make_unique在c++14才加入
auto upw1(std::make_unique<Widget>());
- unique_ptr可以指向数组
std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
std::shared_ptr<int []> ptr2(new int[10]); // error!
- 指定删除器需要确定类型
std::shared_ptr<int> ptr3(new int(1), [](int *p){delete p;});
std::unique_ptr<int> ptr4(new int(1), [](int *p){delete p;}); // error
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;}); // right
weak_ptr
基本用法
// use_count():获取当前观察资源的引用计数
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; // 1
// expired():判断所观察对象是否已经被释放
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
cout << "weak_ptr无效,资源已释放";
else
cout << "weak_ptr有效";
// lock():如果weak_ptr已经过期,返回空的shared_ptr指针;
// 如果未过期,返回一个和当前weak_ptr指向相同的shared_ptr指针
// 因为weak_ptr没有重载* 和->,只能用lock取所指的对象
auto spt = gw.lock();
if(gw.expired()) {
cout << "gw无效,资源已释放";
}else {
cout << "gw有效, *spt = " << *spt << endl;
}
解决循环引用问题
class B;
class C;
class B{
public:
weak_ptr<C> bc; // 将shared_ptr修改为weak_ptr
~B(){
cout << "B destruction" << endl;
}
};
class C{
public:
shared_ptr<B> cb;
~C(){
cout << "C destruction" << endl;
}
};
int main() {
// cicle reference
shared_ptr<B>ptr1(new B);
shared_ptr<C>ptr2(new C);
{
ptr1->bc = ptr2;
ptr2->cb = ptr1;
}
cout << "main" << endl;
return 0;
}
weak_ptr不会增加引用计数,故ptr2的引用计数仍然是1,在离开作用域之后,ptr2的引用计数减为0,会被析构
注意事项
weak_ptr使用前需检查合法性
weak_ptr<int> wp2;
{
shared_ptr<int> sp_ok;
shared_ptr<int> sp(new int(1)); //sp.use_count()==1
wp2 = sp; //wp不会改变引用计数,所以sp.use_count()==1
sp_ok = wp2.lock(); //wp没有重载->操作符。只能这样取所指向的对象
}
if(wp2.expired()) {
cout << "shared_ptr is destroy" << endl;
} else {
cout << "shared_ptr no destroy" << endl;
}
安全性问题
⚠️所管理数据的线程安全性问题⚠️