文章目录
一、RAII机制
RAII(Resource Acquisition is Initialization)是由C++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化。
资源的使用一般需要经历获取、使用和销毁三个步骤,其中资源的销毁往往是程序员容易遗忘的一个环节,所以我们就需要一种让资源自动销毁的方法。C++之父给出的解决方案就是RAII机制,它充分利用了C++语言局部对象自动销毁的特性来控制资源的生命周期。
下面是一个简单的例子:
#include <iostream>
using namespace std;
class Resource {
public:
Resource() {
cout << "Resource()" << endl;
};
void useResource() {
cout << "UseResource()" << endl;
}
virtual ~Resource() {
cout << "~Resource()" << endl;
}
};
class ResourceRAII {
private:
Resource *m_resource;
public:
ResourceRAII() {
m_resource = new Resource();
}
Resource *getResource() {
return m_resource;
}
~ResourceRAII() {
delete m_resource;
}
};
int main() {
ResourceRAII resourceRAII;
auto resource = resourceRAII.getResource();
resource->useResource();
}
atreus@MacBook-Pro % clang++ main.cpp -o main -w -std=c++11
atreus@MacBook-Pro % ./main
Resource()
UseResource()
~Resource()
atreus@MacBook-Pro %
此例中,对于资源类Resource的使用,我们只需要显示地获取和使用即可,在ResourceRAII生命周期结束后会自动将Resource释放,而不需要我们显示调用delete方法来析构Resource。
二、智能指针
智能指针就是一个RAII机制的典型应用。
智能指针是行为类似于指针的类对象,主要包括auto_ptr、unique_ptr和shared_ptr。智能指针的思想就是给指针一个可以释放其指向内存的析构函数,这样不管函数正常还是异常终止,指针及其指向的内存都能被正确回收。
#include <iostream>
#include <memory>
class A {
private:
std::string m_name;
public:
A(std::string name) : m_name(name) {}
~A() { std::cout << "~" + m_name << std::endl; }
};
int main() {
{
A *normal_p = new A{"normal_ptr"}; // 存在内存泄漏
}
{
std::auto_ptr<A> auto_p (new A{"auto_ptr"});
}
{
std::unique_ptr<A> auto_p (new A{"unique_ptr"});
}
{
std::shared_ptr<A> auto_p (new A{"shared_ptr"});
}
return 0;
}
atreus@MacBook-Pro % clang++ -w main.cpp -o main -std=c++11
atreus@MacBook-Pro % ./main
~auto_ptr
~unique_ptr
~shared_ptr
atreus@MacBook-Pro %
尽管auto_ptr、unique_ptr、shared_ptr均为智能指针模板类,但相互之间也有区别:
- 对于auto_ptr,一个显而易见的问题就是如果将一个auto_ptr对象赋值给另一个auto_ptr对象,那么二者将会指向同一块内存,这将导致同一块内存释放两次。
- 为了解决这个问题,shared_ptr会跟踪引用特定对象的智能指针数,这被称为引用计数(类似于操作系统中的软链接),仅当最后一个shared_ptr过期时才会调用delete释放内存。
- 而unique_ptr则通过建立所有权的概念来应对这个问题,对于特定的对象,只能有一个unique_ptr拥有它,也只有拥有对象的unique_ptr才能删除该对象。unique_ptr通过赋值操作转让所有权,但转移过程中的源unique_ptr必须为临时对象,如果源unique_ptr在转移后还会存在一段时间,编译器将禁止这种转移操作以避免对无效数据的访问。此外,unique_ptr还有一个可用于数组的变体,因此在使用
new []
分配内存时,只能使用unique_ptr。
#include <memory>
int main() {
std::unique_ptr<int> p(new int(1));
std::unique_ptr<int> p_;
p_ = p; // not allowed because of error: object of type 'std::unique_ptr<int>' cannot be assigned because its copy assignment operator is implicitly deleted
p_ = std::unique_ptr<int>(new int(1)); // allowed
return 0;
}
三、unique_ptr
unique_ptr不共享它所指向的内容,它本身也无法复制到其他unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何STL算法,只能移动unique_ptr。这意味着在移动unique_ptr时内存资源所有权将转移到另一unique_ptr,并且原始unique_ptr将不再拥有此资源。
unique_ptr实现了独享所有权的语义。一个非空的unique_ptr总是拥有它所指向的资源,拷贝一个unique_ptr将不被允许,因为如果你拷贝了一个unique_ptr,那么拷贝结束后这两个unique_ptr都会指向相同的资源,它们都认为自己拥有这块资源,所以都会企图释放。因此unique_ptr是一个仅能移动(move only)的类型,当指针析构时,它所拥有的资源也被销毁。默认情况下,资源的析构是伴随着调用unique_ptr内部的原始指针的delete操作。
要想创建一个unique_ptr,我们需要将一个new操作符返回的指针传递给unique_ptr的构造函数,或者通过std::move来实现。总之,unique_ptr没有拷贝构造函数,不支持普通的拷贝和赋值操作。
#include <memory>
#include <iostream>
using namespace std;
int main() {
unique_ptr<int> p1(new int(1521));
cout << *p1 << endl;
unique_ptr<int> p2(p1); // error: call to implicitly-deleted copy constructor of 'unique_ptr<int>'
unique_ptr<int> p2 = p1; // error: call to implicitly-deleted copy constructor of 'unique_ptr<int>'
unique_ptr<int> p2 = move(p1);
cout << *p2 << endl;
unique_ptr<int> p3(move(p2));
cout << *p3 << endl;
}
标准库还提供了一个可以管理动态数组的unique_ptr版本unique_ptr<类型[]>
。
#include <memory>
#include <iostream>
using namespace std;
int main() {
unique_ptr<int[]> parr(new int[3] {1, 2, 3});
cout << parr[0] << " " << parr[1] << " " << parr[2] << " " << endl;
}
此外,除了使用std::move转让所有权,从函数中返回时,所有权会自动从一个局部变量中转让到调用函数中,因此可以通过函数返回unique_ptr。
#include <memory>
#include <iostream>
using namespace std;
unique_ptr<int> clone(int x)
{
unique_ptr<int> p(new int(x));
return p; // 返回unique_ptr
}
int main() {
unique_ptr<int> ret = clone(1521);
cout << *ret << endl;
}
unique_ptr大多数情况下都需要与std::move配合使用,从而实现所有权的转让。
#include <memory>
#include <iostream>
using namespace std;
void out(unique_ptr<int> x) {
cout << *x << endl;
}
int main() {
unique_ptr<int> p(new int(1521));
out(move(p));
}
参考:
https://zhuanlan.zhihu.com/p/34660259
https://www.cnblogs.com/DswCnblog/p/5628195.html