“Resource Acquisition is Initialization”
资源获取即初始
RAII中的R代表资源,计算机中的一切可由程序员管理的,如指针的申请的内存,开启占用的文件描述符,创建与销毁的锁等等
对于资源的一般操作即就是
- 申请
- 使用
- 释放
一些常见情况为了避免一些人为与非人为的因素造成资源的未释放或者是未初始化的野指针的问题,如没有delte的内存,没有unlock的锁,所诞生出的技术
std::mutex m;void fn()
{
m.lock();
//read
使用资源
//wrire
m.unlock();
}
如果我们忘记释放锁,或者wirte的时候出现err,或者因为中间庞大的逻辑与架构导致对解锁的管理难以掌控,那么别的线程再也无法加锁成功,而且还会导致一直阻塞
特殊情况
int main()
{
try
{
funcOne();
}
catch (const std::exception& e)
{
cerr << "Exception caught!" << endl;
}
return 0;
}
void funcOne()
{
string str1;
string* str2 = new string();
funcTwo();
delete str2;
}
void funcTwo()
{
ofstrean ofs;
ofs.open("filename");
throw exception();
ofs.close();
}
当functwo的函数抛出异常时,根据栈展开,最近的处理程序在main,那么程序就会跳回main,ofs.close()将没有执行机会,资源就会来不及释放,当然也可以每跳一层释放一层但是会显得逻辑臃肿
为了解决这样的问题
使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入;
二,经典实现 锁RAII最为智慧的一点是将资源的释放方式由程序员控制,将控制时机交给操作系统
templateclass lock_guard { private: T _mutex; public: explicit lock_guard(T &mutex) : _mutex(mutex) { _mutex.lock(); } ~lock_guard() { _mutex.unlock(); } };
像下面这种情况 这把锁就会被初始化时加上,在栈对象被销毁时解锁
std::mutex mut;
int write_to_a_file_descriptor(std::string content)
{
std::lock_guard lock(mut);
// critical area below (might throw exception)
// Writing content to a file descriptor...
// Critical areas above
}
智能指针
对于内存的管理一直是让c/c++程序员头疼的事情,内存泄漏(即 malloc() 内存在对应的 free() 调用执行后永不被释放),对于未初始化的野指针的错误使用,或者错误指向造成的问题,但是我们可以把内存的释放时机交给操作系统来决定
// 使用RAII思想设计的SmartPtr类 templateclass SmartPtr { public: SmartPtr(T* ptr = nullptr) : _ptr(ptr) {} ~SmartPtr() { if(_ptr) delete _ptr; } private: T* _ptr; };
C++为我们提供了四种智能指针但常用只有三种另外一个已经被废除
auto_ptr
C++98提供了auto_ptr的智能指针
auto_ptr的实现原理:管理权转移的思想,即当拷贝和赋值时将智能指针管理的内存地址进行转移,也就是一份空间内存只有一个智能指针进行管理
被废弃原因:当对象拷贝或者赋值后,前面的对象就悬空了
1.unique_ptr
它持有对对象的独有权——两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象。它无法复制到其他 unique_ptr,无法通过值传递, 只能通过移动转移
//unique_ptr:不存在拷贝和赋值-没有管理权的转移 #include#include #include #include template class unqie_ptr { public: constexpr unqie_ptr() = default; explicit unqie_ptr(T *ptr) : _ptr(ptr) { } unqie_ptr(unqie_ptr &rhs) = delete; unqie_ptr(unqie_ptr &&rhs) : _ptr(rhs.realse()){}; unqie_ptr &operator=(std::nullptr_t) { realse(); return *this; } unqie_ptr &operator=(const unqie_ptr &rhs) = delete; unqie_ptr &operator=(unqie_ptr &&rhs) { reset(rhs.realse()); return *this; } explicit operator bool() { return static_cast (_ptr); }; T *get() const noexcept //得到落指针; { return _ptr; } T *realse() //释放权限 { return std::exchange(_ptr, nullptr); } void reset(T *ptr) { delete std::exchange(_ptr, ptr); } void swap(unqie_ptr &rhs) { std::swap(_ptr, rhs._ptr); } ~unqie_ptr() { delete _ptr; } T &operator*() //返回解引用的 { return *_ptr; } T *operator->() { return _ptr; //返回裸指针 }; private: T *_ptr{nullptr}; };
2.shared_ptr
shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:
shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。
引用计数是用来记录资源对象中有多少个指针指向该资源对象。
#include#include #include #include class refcout { public: int use_count() { return _count; } void inc_count() { ++_count; } int dec_count() { --_count; } private: std::atomic_int _count{1}; }; template class sharedptr { public: sharedptr() = default; explicit sharedptr(T *ptr) : _ptr(ptr) { if (ptr) { _rep = new refcout{}; } } sharedptr(const sharedptr &rhs) : _ptr(rhs._ptr), _rep(rhs._rep) { if (_rep) { _rep->inc_count(); } } sharedptr(sharedptr &&rhs) : _ptr(rhs._ptr), _rep(rhs._rep) { rhs._ptr = nullptr; rhs._rep = nullptr; } ~sharedptr() { if (_rep && _rep->dec_count()) { delete _ptr; delete _rep; } } sharedptr &operator=(const sharedptr &rhs) { if (&this != rhs) sharedptr{rhs}.swap(*this); return *this; } sharedptr &operator=(sharedptr &&rhs) { if (&this != rhs) sharedptr{move(rhs)}.swap(*this); return *this; } void swap(sharedptr &rhs) { std::swap(_ptr, rhs._ptr); std::swap(_rep, rhs._rep); } void reset(T *ptr = nullptr) { sharedptr{ptr}.swap(*this); } T *get() { return _ptr; } int us_count() { return _rep ? _rep->use_count() : 0; } T &operator*() { return *_ptr; } T *operator->() { return _ptr; } operator bool() { return static_cast (_ptr); } private: T *_ptr{nullptr}; refcout *_rep{nullptr}; };
3.weak_ptr
一般shared_ptr可以满足资源管理的大部分情况,但是也有些情况是shared_ptr不能处理的,这时候就需要使用weak_ptr
struct tNode
{
int _data;
shared_ptr _prev;
shared_ptr _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr node1(new ListNode);
shared_ptr node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
weak_ptr类的对象它可以指向shared_ptr,并且不会改变shared_ptr的引用计数。一旦最后一个shared_ptr被销毁时,对象就会被释放。
struct tNode
{
int _data;
weak_ptr _prev;
weak_ptr _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr node1(new ListNode);
shared_ptr node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
weak_ptr对象指向shared_ptr对象时,不会增加shared_ptr中的引用计数,因此当node1销毁掉时,则node1指向的空间就会被销毁掉,node2类似,所以weak_ptr指针可以很好解决循环引用的问题。
RAII的特点一般即为,利用构造函数申请资源,利用析构函数释放资源,所以可以不止于此,对于一些其他的资源如文件描述符,都可以利用类对象的特性实现管理
一些特殊实现
5 struct Timer {
6 std::chrono::time_point start, end;
7 std::chrono::duration duration;
8
9 Timer() {
10 start = std::chrono::high_resolution_clock::now();
11 }
12
13 ~Timer() {
14 end = std::chrono::high_resolution_clock::now();
15 duration = end - start;
16
17 float ms = duration.count() * 1000.0f;
18 std::cout << "Timer took " << ms << "ms " << std::endl;
19 }
20 };
21
22 void Function() {
23 Timer timer;// 为整个作用域计时
24
25 for(int i = 0; i < 100; i++){
26 std::cout << "hellon";
27 }
28 }
29
30 int main(int argc, char **argv) {
31 Function();
32
33 std::cin.get();
34 }
~
注:
要做到资源的最精细化管理,还是要自己学会自己动手,因为RaII也无法做到最精细的管理,对比其他类似GO语言等的Gc机制,完全把回收的工作交给操作系统巡回使得解放程序员,但会造成调用时的性能波动开销,也会导致一定的暂停响应状态( 感兴趣的可以下去查一查),因此最精确的内存控制还得靠你的细心思考与设计;



