RAII

文章目录
  1. 1. 什么是RAII
  2. 2. RAII的原理
  3. 3. 应用
  4. 4. 参考资料

什么是RAII

RAII(Resource Acquisition Is Initialization)是由 c++ 之父 Bjarne Stroustrup 提出的,中文翻译为资源获取即初始化,他说:使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入;

RAII的原理

资源的使用一般经历三个步骤

  1. 获取资源
  2. 使用资源
  3. 销毁资源。

    但是资源的销毁往往是程序员经常忘记的一个环节,所以程序界就想如何在程序员中让资源自动销毁呢?c++ 之父给出了解决问题的方案:RAII,它充分的利用了 C++ 语言局部对象自动销毁的特性来控制资源的生命周期。给一个简单的例子来看下局部对象的自动销毁的特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;
class person {
public:
person(const std::string name = "", int age = 0) :
name_(name), age_(age) {
std::cout << "Init a person!" << std::endl;
}
~person() {
std::cout << "Destory a person!" << std::endl;
}
const std::string& getname() const {
return this->name_;
}
int getage() const {
return this->age_;
}
private:
const std::string name_;
int age_;
};
int main() {
person p;
return 0;
}
/*
编译并运行:
g++ person.cpp -o person
./person
运行结果:
Init a person!
Destory a person!
*/

从 person class 可以看出,当我们在 main 函数中声明一个局部对象的时候,会自动调用构造函数进行对象的初始化,当整个 main 函数执行完成后,自动调用析构函数来销毁对象,整个过程无需人工介入,由操作系统自动完成;于是,很自然联想到,当我们在使用资源的时候,在构造函数中进行初始化,在析构函数中进行销毁。整个RAII 过程我总结四个步骤:

  1. 设计一个类封装资源
  2. 在构造函数中初始化
  3. 在析构函数中执行销毁操作
  4. 使用时声明一个该对象的类

应用

linux 下经常会使用多线程技术,说到多线程,就得提到互斥锁,互斥锁主要用于互斥,互斥是一种竞争关系,用来保护临界资源一次只被一个线程访问,按照我们前面的分析,封装一下 POSIX 标准的互斥锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <pthread.h>
#include <cstdlib>
#include <stdio.h>

class Mutex {
public:
Mutex();
~Mutex();

void Lock();
void Unlock();

private:
pthread_mutex_t mu_;

// No copying
Mutex(const Mutex&);
void operator=(const Mutex&);
};


#include "mutex.h"

static void PthreadCall(const char* label, int result) {
if (result != 0) {
fprintf(stderr, "pthread %s: %s\n", label, strerror(result));
}
}

Mutex::Mutex() { PthreadCall("init mutex", pthread_mutex_init(&mu_, NULL)); }

Mutex::~Mutex() { PthreadCall("destroy mutex", pthread_mutex_destroy(&mu_)); }

void Mutex::Lock() { PthreadCall("lock", pthread_mutex_lock(&mu_)); }

void Mutex::Unlock() { PthreadCall("unlock", pthread_mutex_unlock(&mu_)); }

写到这里其实就可以使用 Mutex 来锁定临界区,但我们发现 Mutex 只是用来对锁的初始化和销毁,我们还得在代码中调用 Lock 和 Unlock 函数,这又是一个对立操作,所以我们可以继续使用 RAII 进行封装,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "mutex.h"

class MutexLock {
public:
explicit MutexLock(Mutex *mu)
: mu_(mu) {
this->mu_->Lock();
}
~MutexLock() { this->mu_->Unlock(); }

private:
Mutex *const mu_;
// No copying allowed
MutexLock(const MutexLock&);
void operator=(const MutexLock&);
};

使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "mutexlock.hpp"
#include <unistd.h>
#include <iostream>

#define NUM_THREADS 10000

int num=0;
Mutex mutex;

void *count(void *args) {
MutexLock lock(&mutex);
num++;
}


int main() {
int t;
pthread_t thread[NUM_THREADS];

for( t = 0; t < NUM_THREADS; t++) {
int ret = pthread_create(&thread[t], NULL, count, NULL);
if(ret) {
return -1;
}
}

for( t = 0; t < NUM_THREADS; t++)
pthread_join(thread[t], NULL);
std::cout << num << std::endl;
return 0;
}

/*
编译并运行:g++ test_mutexlock.cpp mutexlock.hpp mutex.cpp mutex.h -o test_mutexlock -lpthread
./test_mutexlock
运行结果:10000 符合预期(可以去掉MutexLock lock(&mutex);试试看看结果如何?)
*/

参考资料