注:本篇耗时较久,先发一部分,每天补一部分
如果说C++对新手最不友好的东西是什么的话,那么堆内存绝对排的上号,初级西甲甲开发工程师们在内存泄漏野指针等问题中水深火热中,甚至于西甲甲老手们都不可避免的忘记释放内存,那么智能指针就是挽救你于水火之中的一个好工具,使用智能指针我们可以轻松的使用指针变量而不用担心内存释放问题。
智能指针的原理
由于对上分配的内存不会自动释放,所以需要我们来手工释放,一旦我们忘记释放了堆上申请的内存则会造成内岑泄漏,而我们对同一块内存释放两次,进程会core掉,报出double free异常,如下示例
// 此处会内存泄漏
void bad_memory_manage1() {
int *pNum = new int;
*pNum++;
}
// 此处会double free
void bad_memory_manage2() {
int *pNum = new int;
*pNum++;
delete pNum;
delete pNum;
}
当然你可能会说,这种问题一下不就看出来了么。是的,这个简单的示例一眼就看出来了,实际的项目中这种内存泄漏或者double free可能埋藏在几十万行代码中,尤其是C++引入了异常机制之后,到处都有可能抛出异常,打断你的处理流程,一旦你异常处理不好,等待你的就是内存泄漏。
面对如上问题我自然而然的想到了如下的方式,我用一个对象封装我使用的资源,这样的话,在这个类声明周期开始的时候自动分配资源,在类对象声明周期结束之后自动释放资源。
class ResourceManager {
public:
ResourceManager(int init_num){
num = new int;
*num = init_num;
}
int get_resource() {
return *num;
}
~ResourceManager() {
delete num;
}
private:
int * num;
};
但是这仍然不能解决问题,如果这个对象拷贝给了其他对象怎么办?如果不重新分配内存会浅拷贝,析构两次,造成double free,如果重新分配内存,那就与我们的初衷不一致了,为了解决这种问题,C++先辈们搞出来的智能指针,把指针对象包装到一个堆对象中,堆对象在声明周期结束后会调用析构函数,此时自动释放之前申请的内存。为了实现自动管理,智能指针引入了引用计数,每增加一个该对象的副本则增加引用计数,每有一个副本被销毁则减少该引用计数,等引用计数为0的时候释放内存。当然还有一些其他辅助智能指针对象。据说智能指针是代理模式的一个实践。下一个主题有了,学习下设计模式中的代理模式。
智能指针的使用
目前C++中使用比较多的是std标注库中的智能指针实现以及boost库(俗称准标准库,C++标准库中的很多特性都是在boost库中直接挪过去的)中的智能指针的实现
boost库
分类及说明
使用示例
boost::scoped_ptr
// A 保证在离开作用域之后它所管理对象能被自动释放
// B scoped_ptr不能通过其他scoped_ptr共享控制权
// C 它们可以交换共享权
// 使用场景:不需要共享使用权,在局部使用的智能指针
void test_scope_ptr() {
// 测试 A
std::cout << "befor scope A" << std::endl;
{
boost::scoped_ptr<Student> pStudent(new Student("zhangsan"));
pStudent->printInfo();
}
std::cout << "after scope A" << std::endl;
// 测试B
std::cout << "befor scope A" << std::endl;
{
boost::scoped_ptr<Student> pStu1(new Student("lisi"));
// boost::scoped_ptr<Student> pStu2 = pStu1; // 此处会直接编译报错
}
std::cout << "after scope B" << std::endl;
// 测试C
std::cout << "befor scope C" << std::endl;
{
boost::scoped_ptr<Student> pStu1(new Student("zhangsan"));
boost::scoped_ptr<Student> pStu2(new Student("lisi"));
pStu1.swap(pStu2);
pStu1->printInfo();
pStu2->printInfo();
}
std::cout << "after scope C" << std::endl;
// 错误使用
// 使用两个智能指针管理统一个裸指针, 会报double free
{
Student* stu = new Student("zhangsan");
boost::scoped_ptr<Student> pStu1(stu);
// boost::scoped_ptr<Student> pStu2(stu); // 先暂时注释掉这个
}
{
boost::scoped_ptr<Student> pStu1;
// scoped_ptr可以直接判断是否为NULL,它重载了operator bool
if (!pStu1)
{
// 可以使用reset重置一个智能指针
pStu1.reset(new Student("wangermazi"));
if (pStu1)
{
pStu1->printInfo();
}
// 可以使用get获取裸指针,注意,没有绝对的理由,不要获取裸指针
Student * pStu = pStu1.get();
pStu->printInfo();
// 使用reset方法可以置空智能指针
pStu1.reset();
std::cout << "after reset the pointer is " << pStu1.get() << std::endl;
}
}
}
shared_ptr的使用示例如下:
boost::shared_ptr<Student> getNewStudent(const std::string& strStudentName) {
boost::shared_ptr<Student> pStu(new Student(strStudentName));
std::cout << pStu.use_count() << std::endl;
return pStu;
}
void test_shared_ptr() {
boost::shared_ptr<Student> pStu;
pStu.reset(new Student("zhangsan"));
boost::shared_ptr<Student> pStu1 = getNewStudent("lisi");
std::cout << "--- use count " << pStu1.use_count() << std::endl;
{
boost::shared_ptr<Student> pStu2 = pStu1;
std::cout << "--- use count " << pStu2.use_count() << std::endl;
std::cout << "--- use count " << pStu1.use_count() << std::endl;
std::cout << pStu1.unique() << endl;
// 声明周期结束之后,引用计数减1
}
std::cout << "--- use count " << pStu1.use_count() << std::endl;
pStu1.get()->printInfo();
// 一旦将智能指针指向的对象重置之后,引用计数就会减1,相应的右操作数引用计数会加1
std::cout << "befor stu1 use count = " << pStu1.use_count() << std::endl;
pStu = pStu1;
std::cout << "after stu1 use count = " << pStu1.use_count() << std::endl;
boost::shared_ptr<Student> pStu2 = pStu1;
std::cout << "after copy construct use count = " << pStu1.use_count() << std::endl;
std::cout << pStu1.unique() << endl;
}
weak_ptr是配合shared_ptr来使用的主要是来解决只能指针中的循环引用问题,比如A对象中有B对象的智能指针,B对象中有A对象的智能指针,那么你创建的时候A和B就会相互引用。比较优雅的解决办法就是使用weak_ptr
// 为何要引入weak_ptr?
// 如果出现了循环引用,则会出现内存泄漏问题
void test_weak_ptr() {
boost::shared_ptr<Student> pStu(new Student("zhangsan"));
std::cout << pStu.use_count() << std::endl;
boost::weak_ptr<Student> pwStu = pStu;
std::cout << pStu.use_count() << std::endl;
{
// lock方法可以返回shared_ptr
boost::shared_ptr<Student> pStu2 = pwStu.lock();
std::cout << pStu.use_count() << std::endl;
std::cout << pwStu.use_count()<< std::endl;
}
boost::weak_ptr<Student> pwstu1;
boost::shared_ptr<Student> pstu2 = pwstu1.lock();
{
boost::shared_ptr<Student> pstu3(new Student("wangwu"));
pwstu1 = pstu3;
pwstu1.lock()->printInfo();
std::cout << pwstu1.expired() << std::endl;
}
std::cout << pwstu1.expired() << std::endl;
std::cout <<( pstu2 ? "true" : "false") << std::endl;
std::cout << pstu2.use_count() << std::endl;
std::cout << pwstu1.use_count() << std::endl;
std::cout << pStu.use_count() << std::endl;
std::cout << pwStu.use_count()<< std::endl;
}