用对象来管理资源
std::auto_ptr
与 std::shared_ptr
,通常 std::shared_ptr
会更好。但是在使用时一定要将对象指针存储于智能指针对象内,智能指针只能对阵堆上的内存管理class Investment {
...
};
Investment * createInvestment() { // 对象创建函数,返回一个在堆上创建后的Investment指针,此时需要使用delete释放
Investment* inv = new Investment();
return inv;
}
void f() {
Investment* pInv = createInvestment();
...
delete pInv;
}
// 若早 ... 之间发生return或异常跳出,此时pInv指向的空间就不会被释放,发生内存泄露
// 因此我们需要构造一种对象来构造和释放资源,使得程序在任何地方跳出时都能够保证内存被析构
// 此时就引出了经典的智能指针
void f2() {
std::shared pInv(createInvestment()) ;
...
}
// 此时不需要delete来进行析构,智能指针会在当前对象的计数变为0时自动析构,这算是智能指针最经典的用法
智能指针 shared_ptr
的实现
template
class SharedPtr {
public:
SharedPtr() : ptr(nullptr), count(nullptr) { }
SharedPtr(T *p, std::size_t n = 1) : ptr(p), count(new std::size_t(n)) { } // 无法防止循环引用,若用同一个普通指针去初始化两个shared_ptr,此时两个shared_ptr均指向同一片区域,但引用计数都为1,使用时需注意
SharedPtr(const SharedPtr& p) : ptr(p.ptr), count(&(++(*p.count))) { }
// 析构函数
~SharedPtr() {
if (ptr && --*count == 0) {
delete ptr;
delete count;
}
ptr = nullptr;
count = nullptr;
}
// 赋值运算符1,首先this的引用计数减一,p的引用计数加1
//SharedPtr& operator=(const SharedPtr& p) {
// if (*this == p) {
// return *this;
// }
// if (ptr && --*count == 0) { // -1之后若为0,则释放
// delete ptr;
// delete count;
// }
// ++(*p.count);
// ptr = p.ptr;
// count = p.count;
//}
// 赋值运算符2
SharedPtr& operator=(const SharedPtr &p) {
SharedPtr(p).swap(*this);
return *this;
}
std::size_t size() const {
return *count;
}
bool unique() const {
return *count == 1;
}
T* get() const {
return ptr;
}
// reset 次数
void reset(T* ptr, std::size_t co) {
SharedPtr(ptr, co).swap(*this);
}
void swap(SharedPtr &p) {
using std::swap;
swap(count, p.count);
swap(ptr, p.ptr);
}
// *运算符
T& operator*() const {
return *ptr;
}
// ->运算符
T* operator->() const {
return ptr;
}
private:
T *ptr;
std::size_t *count; // 计数器,使用int* 比 int 好
};
资源管理类中小心 copy
行为
由于智能指针只能针对堆上内存管理,并非所有的资源都是堆上资源,所以很多时候我们都需要建立自己的资源管理类。
shared_ptr
作为成员变量)一个典型的例子为互斥锁的锁定与解锁行为,我们不想在程序释放时还有资源处在上锁状态,则需要保证程序跳出时会解锁所有的上锁资源
互斥器对象 Mutex
有两个函数用于锁定与解锁,void lock(Mutex *pm)
与 void unlock(Mutex *pm)
class Lock {
public:
explicit Lock(Mutex *pm) : mutexPtr(pm) {
lock(mutexPtr); // 加锁
}
~Lock() {
unlock(mutexPtr); // 解锁
}
private:
Mutex *mutexPtr;
};
这种情况下是能够实现资源的加锁与解锁问题,但是当Lock对象被复制时,会出现问题。
我们想要的是锁被多个内容占用时,不是立刻解除所有的锁,而是等程序都处理完之后是才会解除锁定
此时可能又需要引用计数来进行加锁。但只是将指针改为智能指针后,当计数为0是,智能指针会删除锁,而我们想要的是解锁,此时需要使用智能指针指定所谓的删除器(deleter
),他是一个函数或函数对象,当引用次数为0时就要被调用,默认是删除指针。
class Lock {
public:
explicit Lock(Mutex *pm) : mutexPtr(pm, unlock) { // 用unlock()指定删除器
lock(mutexPtr); // 加锁
}
private:
std::shared_ptr mutexPtr;
};
本例中没有必要指定析构函数。可以使用默认的析构函数用来释放智能指针,而此时会调用智能指针的析构函数,从而满足需求。
类型转换函数的实现,以智能指针为例
类型转换函数的语法为 operator name() {}
,其中 name
是要转换的目的类型
class SharedPtr {
...
operator pointer() const { // 当要将其转化为pointer类型的对象时,会隐式调用该类型转换
return ptr;
}
...
}
如上面智能指针实现过程中的 get()
函数,调用它可以获得原始的指针。
成对地使用 new
和 delete
,并要采取相同的形式 new []
对应delete []
,实际上是通过 operator new
和 operator delete
函数实现的。
以独立的一条语句将 new
对象存储于只能指针中,若不这样做,可能会导致难以察觉的资源泄露
// 函数
void processWidget(std::shared w, int priority);
// processWidget(std::shared(new Widget), priority()); // 可能会导致内存泄露
// 因为我们不知道priority()在什么时候执行,万一在执行 new Widget 后执行则会出现异常,导致内存泄露
// 因此我们要采用多条语句的方式
std::shared w(new Widget); // 先创建只能指针对象
processWidget(w, priority()); // 这样就不会导致内存泄露了