智能指针是可以保存指向动态分配内存的对象指针的类,它可以确保在自身周期结束的时候自动的销毁动态分配内存的对象,因此它可以有效的防止内存泄露。c++11中提供了三种智能指针,std::weak_ptr、std::shared_ptr和std::unique_ptr。
内存泄露
内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。在项目开发中,内存泄露其实比较严重的问题,最终甚至会导致系统崩溃。
shared_ptr
从名字可以看出来,这是一种可以“共享的指针“的类。它本身包含一个引用计数,初始化时保存指针将引用计数置为1,通过拷贝构造函数构造另外一个对象时引用计数+1;对一个对象进行赋值时,被赋值的对象的引用计数-1,赋值的对象的引用计数+1。每个对象析构时,引用计数-1,直到引用计数为0时,会自动的保存的指针。
1.初始化
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> ptr1(new int(10));
std::cout << "use_cout :" << ptr1.use_count() << std::endl; // 1
std::shared_ptr<int> ptr2 = ptr1;
std::cout << "use_cout :" << ptr1.use_count() << std::endl; // 2
std::cout << "use_cout :" << ptr2.use_count() << std::endl; // 2
std::shared_ptr<int> ptr3;
std::cout << "use_cout :" << ptr3.use_count() << std::endl; // 0
ptr3.reset(new int(10));
std::cout << "use_cout :" << ptr3.use_count() << std::endl; // 1
return 0;
}
使用**use_count()**可以输出引用计数。
2.使用自定义的删除器
当引用计数为0时,智能指针会自动析构动态内存,默认的删除方式是delete。但是有时候我们使用的指针比较复杂一些,例如是动态分配的数组等,这个时候可以采用自定义的deleter。
#include <memory>
#include <iostream>
struct Person {
char* name;
int name_len;
};
void Deleter(Person* person) {
if (nullptr != person) {
if (nullptr != person->name) {
delete[] person->name;
person->name = nullptr;
std::cout << "delete name" << std::endl;
}
delete person;
person = nullptr;
std::cout << "delete person" << std::endl;
}
}
int main() {
std::shared_ptr<Person> ptr1(new Person{nullptr, 0}, &Deleter);
ptr1->name_len = 10000000;
ptr1->name = new char[ptr1->name_len];
return 0;
}
3.循环引用问题
这是智能指针使用时最可能踩坑的地方。
#include <memory>
#include <iostream>
class Parent;
class Son;
class Parent {
public:
~Parent() {
std::cout << "delete parent" << std::endl;
}
std::shared_ptr<Son> ptr;
};
class Son {
public:
~Son() {
std::cout << "delete son" << std::endl;
}
std::shared_ptr<Parent> ptr;
};
int main() {
std::shared_ptr<Parent> parent(new Parent);
std::shared_ptr<Son> son(new Son);
parent->ptr = son;
son->ptr = parent;
return 0;
}
运行之后可以发,没有任何打印,也就是说parent和son都没有析构掉,存在内存泄露!那为什么没有析构掉呢?莫非是引用计数最后不会减到0?我们添加输出信息。
int main() {
std::shared_ptr<Parent> parent(new Parent);
std::shared_ptr<Son> son(new Son);
std::cout << parent.use_count() << std::endl; // 1
std::cout << son.use_count() << std::endl; // 1
parent->ptr = son;
std::cout << parent.use_count() << std::endl; // 1
std::cout << son.use_count() << std::endl; // 2
son->ptr = parent;
std::cout << parent.use_count() << std::endl; // 2
std::cout << son.use_count() << std::endl; // 2
return 0;
}
从输出我们可以看到,由于循环引用,导致parent和son的引用计数都为2,生命周期结束时,各自减1之后不为0,因此不会销毁而导致内存泄露。通过weak_ptr可解决这个问题。
weak_ptr
名字看起来就比较弱,没有重载*和->操作符,不共享指针,不会使引用计数加1。它主要是为了监视shared_ptr的生命周期。
1.初始化
int main() {
std::shared_ptr<Person> sh_ptr(new Person{nullptr, 10});
std::weak_ptr<Person> w_ptr(sh_ptr);
std::cout << sh_ptr.use_count() << std::endl;
std::cout << w_ptr.use_count() << std::endl;
return 0;
}
2.监视shared_ptr
int main() {
std::weak_ptr<Person> w_ptr;
{
std::shared_ptr<Person> sh_ptr(new Person{nullptr, 10});
w_ptr = sh_ptr;
std::cout << sh_ptr.use_count() << std::endl; // 1
std::cout << w_ptr.use_count() << std::endl; // 1
if (w_ptr.expired()) {
std::cout << "expired" << std::endl;
} else {
std::cout << w_ptr.lock()->name_len << std::endl; // 10
}
}
std::cout << w_ptr.use_count() << std::endl; // 0
if (w_ptr.expired()) {
std::cout << "expired" << std::endl; // expired
} else {
std::cout << w_ptr.lock()->name_len << std::endl;
}
return 0;
}
expired()用于判断所监视shared_ptr的管理的内存是否释放掉;lock()获取被监视的shared_ptr。
3.解决循环引用问题
class Parent {
public:
~Parent() {
std::cout << "delete parent" << std::endl; // delete parent
}
std::shared_ptr<Son> ptr;
};
class Son {
public:
~Son() {
std::cout << "delete son" << std::endl; // delete son
}
std::weak_ptr<Parent> ptr;
};
int main() {
std::shared_ptr<Parent> parent(new Parent);
std::shared_ptr<Son> son(new Son);
std::cout << parent.use_count() << std::endl; // 1
std::cout << son.use_count() << std::endl; // 1
parent->ptr = son;
std::cout << parent.use_count() << std::endl; // 1
std::cout << son.use_count() << std::endl; // 2
son->ptr = parent;
std::cout << parent.use_count() << std::endl; // 1
std::cout << son.use_count() << std::endl; // 2
return 0;
}
将son中的成员ptr改成了weak_ptr,这样在son->ptr = parent时,就不会增加引用计数,因此parent的引用仍然是1。这样在parant生命周期结束时,parent的引用计数减1成为0,这样保存的Parent指针会被析构,首先打印出delete parent。接着由于Parent析构,内部元素son的引用计数减1成为1,生命周期结束后,son的引用计数再减1变为0,也会被析构掉。
unique_ptr
独享指针,即不能将unique_ptr赋值给另外一个unique_ptr。例如下面的使用是错误的:
1.基本使用
std::unique_ptr<int> u_ptr1(new int);
std::unique_ptr<int> u_ptr2(u_ptr2);
编译错误:
/usr/include/c++/4.8/bits/unique_ptr.h:273:7: error: declared here
unique_ptr(const unique_ptr&) = delete;
但是可以通过std::move来转移
std::unique_ptr<int> u_ptr1(new int);
std::unique_ptr<int> u_ptr2 = std::move(u_ptr1);
2.使用自定义的删除器
struct Deleter {
void operator()(Person* person) {
if (nullptr != person) {
if (nullptr != person->name) {
delete[] person->name;
person->name = nullptr;
std::cout << "delete name" << std::endl;
}
delete person;
person = nullptr;
std::cout << "delete person" << std::endl;
}
}
};
int main() {
std::unique_ptr<Person, Deleter> ptr1(new Person{nullptr, 0});
ptr1->name_len = 10000000;
ptr1->name = new char[ptr1->name_len];
return 0;
}
可以看到unique_ptr与shared_ptr添加deleter还是有区别的。unique_ptr出来像上面那样添加deleter,还可以类似下面这样通过lambda表达式添加deleter:
std::unique_ptr<Person, void(*)(Person *)> ptr2(new Person{nullptr, 0},
[](Person* person) {
if (nullptr != person) {
if (nullptr != person->name) {
delete[] person->name;
person->name = nullptr;
std::cout << "delete name" << std::endl;
}
delete person;
person = nullptr;
std::cout << "delete person" << std::endl;
}});
下面介绍如何简单的实现shared_ptr和unique_ptr。