对软件开发人员来说,管理资源时一项基本的任务。大量的资源必须合理分配、使用和归还。
主要的资源包括:
1、内存(栈、堆);
2、访问硬盘或者其他的介质(如网络)上的文件(读/写)所需的文件句柄;
3、网络连接(例如:连接服务器、数据库等);
4、线程、锁、定时器和事务;
5、其他操作系统资源,如Windows操作系统上的GDI句柄。
一、资源申请即初始化
资源申请即初始化(Resource Acquisition is Initiazation, RAII),该术语也被称为“构造时获得,析构时释放”。
例如:
class ResType
{
public:
ResType() {};
~ResType() {};
void foo() { cout << "ResType" << endl; };
};
template <typename T>
class ScopedResource final
{
public:
ScopedResource() { managedResource = new T(); }
~ScopedResource() { delete managedResource; }
T* operator->() const { return managedResource;}
private:
T* managedResource;
};
void doSomething()
{
ScopedResource<ResType> resource;
try
{
resource->foo();
}
catch (const std::exception&)
{
throw;
}
}
二、智能指针
智能指针减少了内存泄漏的可能性,此外,他们被设计成线程安全的。
2.1 独占所有权的std::unique_ptr<T>
std::unique_ptr<T>模板类(定义在<memory>头文件中)管理了一个指向T类型对象的指针。这个智能指针提供的是独占的所有权,也就是,一个对象一次只能由std::unique_ptr<T>的一个实例拥有。
示例:
#include<iostream>
#include<vector>
using ResourceTypePtr = std::unique_ptr<ResType>;
using ResourceVector = std::vector<ResourceTypePtr>;
ResourceTypePtr resource{ std::make_unique<ResType>() };
ResourceVector aCollectionOfResource;
aCollectionOfResource.push_back(std::move(resource));
需要注意的是,std::vector::push_back()分别调用了std::unique_ptr<T>的移动构造函数和移动赋值操作符,resource不再管理对象,并被表示为空。
警告:
不要在代码中使用std::auto_ptr<T>!
随着C++11标准的发布,std::auto_ptr<T>已被标记为“弃用”,并且不再使用。这个智能指针的实现不支持rvalue引用和Move语义,并且也不能存储到STL库的容器中。
std::unique_ptr<T>是std::auto_ptr<T>的完美替代者。
不允许调用std::unique_ptr<T>的拷贝构造函数,然而,使用Move语义可以把std::unique_ptr<T>持有的资源转移给另一个std::unique_ptr<T>实例,如下所示:
std::unique_ptr<ResType> pointer1 = std::make_unique<ResType>();
std::unique_ptr<ResType> pointer2; // pointer2是空的
pointer2 = std::move(pointer1); // 此时pointer1是空的,pointer2是新的持有者
2.2 共享指针std::shared_ptr<T>
std::shared_ptr<T>模板类(定义在<memory>头文件中)管理了一个指向T类型对象的指针,可以与std::shared_ptr<T>的其他实例共享这个所有权。换句话说,T类型的一个实例的所有权以及删除它的责任,可以有许多共享这个实例的所有者(std::shared_ptr<T>的实例)接管。
std::shared_ptr<T>提供了一个简单且有限的垃圾回收功能。这个智能指针的内部实现有一个引用计数器,用于监视当前有多少个std::shared_ptr<T>的实例。如果智能指针的最后一个实例被销毁,智能指针就会释放它持有的资源。
std::shared_ptr<ResType> pointer3 = std::make_shared<ResType>();
std::shared_ptr<ResType> pointer4;
pointer4 = std::move(pointer3); //pointer3指向的资源被移动到pointer4中,pointer3即为空
std::shared_ptr<ResType> pointer5(pointer4);
std::shared_ptr<ResType> pointer6(pointer4);
上面的例子并没有修改智能指针的引用计数,但是在移动后必须小心使用pointer3变量,因为这个变量是空的了,也就是说,它持有一个nullptr。
2.3 弱智能指针std::weak_ptr<T>
一个没有持有资源的指针指向一个或者多个std::shared_ptr<T>实例持有的资源时非常有必要的。
std::weak_ptr<T>仅仅观察它指向的资源,并检查改资源是否有效。
weak_ptr常用函数介绍:
- use_count函数,表示当前引用计数
- reset函数,表示删除当前被管理的对象
- expired函数,表示判断被管理的对象是否被删除,相当于use_count()==0,如果被删除则返回true,否则返回false
- swap函数,表示交换weak_ptr管理对象和swap参数里边的内容
示例:
void doSomething(const std::weak_ptr<ResType>& weakResource)
{
if (!weakResource.expired())
{
// 现在我们知道weakResource指向的对象是有效的
std::shared_ptr<ResType> shareResource = weakResource.lock(); // 保证线程安全
// Use shareResource
std::cout << "weakResource is valid!" << std::endl;
}
else
{
std::cout << "weakResource is invalid!" << std::endl;
}
}
int main()
{
auto sharedResource(std::make_shared<ResType>());
std::weak_ptr<ResType> weakRource(sharedResource);
doSomething(weakRource);
sharedResource.reset(); //删除sharedResource指向ResType的实例
doSomething(weakRource);
system("pause");
return 0;
}
std::weak_ptr<T>的expired()成员函数来检查它指向的资源是否有效。
std::weak_ptr<T>不提供指针解引用操作符,如*或->。如果要使用其“观察”的资源,首先必须调用lock()成员函数获取一个std::shared_ptr<T>的实例。
std::weak_ptr<T>使用场景
1、使用std::weak_ptr<T>和std::shared_ptr<T>,能够明确区分软件设计中的资源所有者和资源使用者。并不是每个软件单元都想成为资源的所有者,因为他们只需要资源完成特定的、有时间限制的任务。正如在doSomething()函数中显示,有时候仅仅在一段时间内将弱指针“提升”为强指针就足够了。
例如:
1)在使用缓存对象的地方,std::weak_ptr<T>的实例用于指向这些对象,但是不拥有对象的所有权。如果std::weak_ptr<T>实例的expire()成员函数返回true,那么垃圾回收进程就从缓存中清除了该指针指向的对象。
2)可以使用std::weak_ptr<T>::lock()函数获取std::shared_ptr<T>的实例,这样,即使垃圾回收进程处于活动状态,也可以安全地使用这个对象。因此,对象的生命周期被延长了,或者当引用计数为0时,垃圾回收进程从缓存中删除这个对象,这样不会影响它的使用者。
3)在处理循环依赖的问题。>> 如果你有一个类A需要拥有一个指向另一个类B的指针,同时,类B需要拥有一个指向类A的指针,这样就会产生循环依赖问题。
代码如下:
#include<iostream>
#include<memory>
#include<boost/make_shared.hpp>
#include<boost/shared_array.hpp>
#include<boost/shared_ptr.hpp>
using namespace std;
class B; //类的前置声明
class A
{
public:
A() { cout << "structure A" << endl; }
~A() { cout << "release A" << endl; }
void setB(std::shared_ptr<B>& pointerToB)
{
myPointerToB = pointerToB;
}
private:
std::shared_ptr<B> myPointerToB;
};
class B
{
public:
B() { cout << "structure B" << endl; }
~B() { cout << "release B" << endl; }
void setA(std::shared_ptr<A>& pointerToA)
{
myPointerToA = pointerToA;
}
private:
std::shared_ptr<A> myPointerToA;
};
int main()
{
auto pointerToA = std::make_shared<A>();
auto pointerToB = std::make_shared<B>();
pointerToA->setB(pointerToB);
pointerToB->setA(pointerToA);
pointerToA.reset();
pointerToB.reset();
// 此时A和B的实例已经无法访问了,但是A和B的实例又没有被释放(内存泄漏)
system("pause");
return 0;
}
如果将A和B类中的std::shared_ptr<T>的成员变量类型替换为std::weak_ptr<T>类型,就能解决内存泄漏的问题。
代码如下:
class B; //类的前置声明
class A
{
public:
void setB(std::shared_ptr<B>& pointerToB)
{
myPointerToB = pointerToB;
}
private:
std::weak_ptr<B> myPointerToB;
};
class B
{
public:
void setA(std::shared_ptr<A>& pointerToA)
{
myPointerToA = pointerToA;
}
private:
std::weak_ptr<A> myPointerToA;
};
基本上来说,循环依赖是应用程序代码中糟糕的设计,应该尽可能避免循环依赖。