目录
一、为什么需要智能指针?
二、智能指针的原理及使用
三、auto_ptr
3.1 - 基本使用
3.2 - 模拟实现
四、unique_ptr
4.1 - 基本使用
4.2 - 模拟实现
5.1 - 基本使用
5.2 - 模拟实现
六、weak_ptr
6.2 - 模拟实现
七、定制删除器
一、为什么需要智能指针?
问题引入:
#include <iostream>
using namespace std;
int division(int x, int y)
{if (y == 0)throw "Division by zero condition!";elsereturn x / y;
}
void func()
{string* p1 = new string("hello");pair<string, string>* p2 = new pair<string, string>{ "hello", "你好" };
int a = 0, b = 0;cin >> a >> b;cout << division(a, b) << endl;
delete p1;delete p2;
}
int main()
{try {func();}catch (const char* errmsg) {cout << errmsg << endl;}catch (...) {cout << "Unknow exception" << endl;}return 0;
}
调用 func 函数:
如果在执行
string* p1 = new string("hello");
语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常;如果在执行
pair<string, string>* p2 = new pair<string, string>{ "hello", "你好" };
语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 指向的动态分配的内存没有被释放,最终会造成内存泄漏;如果在调用
division(a, b)
函数的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 以及 p2 指向的动态分配的内存都没有被释放,最终也会造成内存泄漏。
二、智能指针的原理及使用
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,对资源的访问在对象生命周期内始终有效;在对象析构时,即对象生命周期结束时,释放资源。
SmartPtr.h:
#pragma once
namespace yzz
{template<class T>class SmartPtr{public:SmartPtr(T* ptr = nullptr) : _ptr(ptr) { }
~SmartPtr(){if (_ptr){delete _ptr;_ptr = nullptr;}}T& operator*(){return *_ptr;}
T* operator->(){return _ptr;}private:T* _ptr;};
}
test.cpp:
#include <iostream>
#include "SmartPtr.h"
using namespace std;
int division(int x, int y)
{if (y == 0)throw "Division by zero condition!";elsereturn x / y;
}
void func()
{yzz::SmartPtr<string> sp1 = new string("hello");cout << *sp1 << endl;
yzz::SmartPtr<pair<string, string>> sp2 = new pair<string, string>{ "hello", "你好"};cout << (*sp2).first << " : " << (*sp2).second << endl;cout << sp2->first << " : " << sp2->second << endl; // 为了可读性,编译器将 sp2->->first/second 优化成了 sp2->first/second
int a = 0, b = 0;cin >> a >> b;cout << division(a, b) << endl;// 在 func 函数中,无论有没有异常抛出,// func 函数结束后,都会自动调用 sp1 以及 sp2 对象的析构函数释放动态分配的内存,// 不再需要手动 delete
}
int main()
{try {func();}catch (const char* errmsg) {cout << errmsg << endl;}catch (...) {cout << "Unknow exception" << endl;}return 0;
}
三、auto_ptr
C++98/03 标准中提供了 auto_ptr 智能指针。
3.1 - 基本使用
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
~A(){cout << "~A()" << endl;}
int _i;
};
int main()
{auto_ptr<A> ap1(new A(1));auto_ptr<A> ap2(new A(2));
auto_ptr<A> ap3(ap1); // 管理权转移auto_ptr<A> ap4(new A(4));ap4 = ap2; // 管理权转移
// cout << ap1->_i << endl; // error// cout << ap2->_i << endl; // errorcout << ap3->_i << endl; // 1cout << ap4->_i << endl; // 2return 0;
}
3.2 - 模拟实现
namespace yzz
{template<class T>class auto_ptr{public:auto_ptr(T* ptr = nullptr) : _ptr(ptr){ }
~auto_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}
auto_ptr(auto_ptr<T>& ap): _ptr(ap._ptr){ap._ptr = nullptr;}
auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != &ap){if (_ptr)delete _ptr;
_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}
T& operator*(){return *_ptr;}
T* operator->(){return _ptr;}private:T* _ptr;};
}
四、unique_ptr
C++11 标准废弃了 auto_ptr,新增了 unique_ptr、shared_ptr 以及 weak_ptr 这三个智能指针。
4.1 - 基本使用
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
~A(){cout << "~A()" << endl;}
int _i;
};
int main()
{unique_ptr<A> up1(new A(1));unique_ptr<A> up2(new A(2));
// unique_ptr<A> up3(up1); // errorunique_ptr<A> up4(new A(4));// up4 = up2; // errorreturn 0;
}
4.2 - 模拟实现
从上面的例子中可以看出,unique_ptr 的实现原理就是简单粗暴的防拷贝。
namespace yzz
{template<class T>class unique_ptr{public:unique_ptr(T* ptr = nullptr) : _ptr(ptr){ }
~unique_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}
unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
T& operator*(){return *_ptr;}
T* operator->(){return _ptr;}private:T* _ptr;};
}
五、shared_ptr
5.1 - 基本使用
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
~A(){cout << "~A()" << endl;}
int _i;
};
int main()
{shared_ptr<A> sp1(new A(1));shared_ptr<A> sp2(new A(2));cout << sp1.use_count() << endl; // 1cout << sp2.use_count() << endl; // 1cout << "-------------" << endl;
shared_ptr<A> sp3(sp1);cout << sp1.use_count() << endl; // 2cout << sp3.use_count() << endl; // 2sp3->_i *= 10;cout << sp1->_i << endl; // 10cout << sp3->_i << endl; // 10cout << "-------------" << endl;
shared_ptr<A> sp4(new A(4));sp4 = sp2;cout << sp2.use_count() << endl; // 2cout << sp4.use_count() << endl; // 2sp4->_i *= 10;cout << sp2->_i << endl; // 20cout << sp4->_i << endl; // 20return 0;
}
5.2 - 模拟实现
shared_ptr 的实现原理:通过引用计数的方式实现多个 shared_ptr<T> 类对象之间共享资源。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1,每增加一个对象使用该资源,就给计数增加 1,当某个对象被销毁时,先给该计数减 1,然后再检查是否需要释放资源,如果计数为 0,说明该对象是资源的最后一个使用者,于是将资源释放,否则就不能释放,因为还有其他对象在使用该资源。
namespace yzz
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr): _ptr(ptr), _pCnt(new int(1)){ }
~shared_ptr(){if (--(*_pCnt) == 0){if (_ptr){delete _ptr;_ptr = nullptr;}delete _pCnt;}}
shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr), _pCnt(sp._pCnt){++(*_pCnt);}
shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){if (--(*_pCnt) == 0){if (_ptr)delete _ptr;
delete _pCnt;}
_ptr = sp._ptr;_pCnt = sp._pCnt;++(*_pCnt);}return *this;}
T& operator*(){return *_ptr;}
T* operator->(){return _ptr;}
int use_count() const{return *_pCnt;}
T* get() const{return _ptr;}private:T* _ptr;int* _pCnt;};
}
六、weak_ptr
6.1 - shared_ptr 的循环引用问题
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
~A(){cout << "~A()" << endl;}
int _i;
};
struct ListNode
{A _val;shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;
};
int main()
{shared_ptr<ListNode> sp1(new ListNode);shared_ptr<ListNode> sp2(new ListNode);// A(int x = 0)// A(int x = 0)cout << sp1.use_count() << endl; // 1cout << sp2.use_count() << endl; // 1
sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.use_count() << endl; // 2cout << sp2.use_count() << endl; // 2// 出现内存泄漏问题return 0;
}
解决方案:
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
~A(){cout << "~A()" << endl;}
int _i;
};
struct ListNode
{A _val;// weak_ptr 不增加引用计数,并且在可以访问资源的同时,不参与释放资源weak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;
};int main()
{shared_ptr<ListNode> sp1(new ListNode);shared_ptr<ListNode> sp2(new ListNode);// A(int x = 0)// A(int x = 0)cout << sp1.use_count() << endl; // 1cout << sp2.use_count() << endl; // 1sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.use_count() << endl; // 1cout << sp2.use_count() << endl; // 1// ~A()// ~A()return 0;
}
6.2 - 模拟实现
namespace yzz
{template<class T>class weak_ptr{public:weak_ptr() : _ptr(nullptr){ }
weak_ptr(const shared_ptr<T>& sp): _ptr(sp.get()){ }
weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}
T& operator*(){return *_ptr;}
T* operator->(){return _ptr;}private:T* _ptr;};
}
七、定制删除器
template<class T>
struct DeleteArrayFunc
{void operator()(T* ptr){delete[] ptr;}
};
template<class T>
struct FreeFunc
{void operator()(T* ptr){free(ptr);}
};
int main()
{shared_ptr<A> sp1(new A(1)); // ok
// shared_ptr<A> sp2(new A[5]); // errorshared_ptr<A> sp2(new A[5], DeleteArrayFunc<A>()); // ok
// shared_ptr<A> sp3((A*)malloc(sizeof(A))); // errorshared_ptr<A> sp3((A*)malloc(sizeof(A)), FreeFunc<A>()); // ok
// shared_ptr<FILE> sp4(fopen("test.cpp", "r")); // errorshared_ptr<FILE> sp4(fopen("test.cpp", "r"), [](FILE* fp) { fclose(fp); }); // errorreturn 0;
}
shared_ptr 的模拟实现:
#include <functional>
namespace yzz
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr): _ptr(ptr), _pCnt(new int(1)), _del([](T* ptr) { delete ptr; }){ }
template<class D>shared_ptr(T* ptr, D del): _ptr(ptr), _pCnt(new int(1)), _del(del){ }
~shared_ptr(){if (--(*_pCnt) == 0){if (_ptr){_del(_ptr);_ptr = nullptr;}delete _pCnt;}}// ... ...private:T* _ptr;int* _pCnt;std::function<void(T*)> _del;};
}