RAII与Pimpl
2013-11-12 19:40
246 查看
RAII是Bjarne
Stroustrup教授用于解决资源分配而发明的技术,资源获取即初始化。
RAII是C++的构造机制的直接使用,即利用构造函数分配资源,利用析构函数来回收资源。
我们知道,在C/C++语言中,对动态分配的内存的处理必须十分谨慎。在没有RAII应用的情况下,如果在内存释放之前就离开指针的作用域,这时候几乎没机会去释放该内存,除非垃圾回收器对其管制,否则我们要面对的将会是内存泄漏。
举个例子来说明下RAII在内存分配方面的使用。
这是典型的C风格代码,没有应用RAII。
因此值得注意的是,destroy_bytearray必须在退出作用域前被调用。
然而在复杂的逻辑设计中,程序员往往要花大量的精力以确认所有在该作用域分配的ByteArray得到正确的释放。
相形之下,C++运行机制保证了栈上对象一旦即将离开作用域,其析构函数将被执行,给予了释放资源的时间。注意,在堆分配的对象必须调用delete来结束其生命。
C++11 STL中的std::unique_ptr可用于控制作用域中的动态分配的对象。
譬如:
函数bar()只是增加了一行,但强壮了很多,函数bar()执行完或者有异常抛出时,holder总会被析构,从而ba或被delete。
下面是ByteArray的Ada实现:
– 输出如下
./main
Create
Initialize
Finalize
Finalize
另一种情况是对I/O资源的处理,当我们不再使用资源时,必须将资源归还给系统。
下面例子来自 wikipedia的RAII条目:
在write_to_file函数中,RAII作用于std::ofstream和std::lock_guard,从而保证了函数write_to_file在返回时,lock和file总会调用自身的析构函数,对于lock而言,它会释放mutex,而file则会close。
Pimpl(pointer
to implementation),是一种应用十分广泛的技术,它的别名也很多,如Opaque pointer, handle classes等。
wikipedia上已经对其就Ada、C和C++举例,这里不作举例。
个人认为,Pimpl是RAII的延展,籍由RAII对资源的控制,把具体的数据布局和实现从调用者视线内移开,从而简化了API接口,也使得ABI兼容变得有可能,Qt和KDE正是使用Pimpl来维护ABI的一致性,另外也为惰性初始化提供途径,以及隐式共享提供了基础。
我在设计代码时也会考虑使用Pimpl,但不是必然使用,因为Pimpl也会带来副作用,主要有两方面
Pimpl指针导致内存空间开销增大
类型间Pimpl的访问需要较多间接的指针跳转,甚至还用使用friend''来提升访问权限,如以下代码中,Teacher可以访问Student的Context。
尽管如此,我个人还是在面向开发应用的接口中会尽量使用Pimpl来维护API和ABI的一致性,除非Pimpl会引起显著的性能下降。
Stroustrup教授用于解决资源分配而发明的技术,资源获取即初始化。
RAII是C++的构造机制的直接使用,即利用构造函数分配资源,利用析构函数来回收资源。
我们知道,在C/C++语言中,对动态分配的内存的处理必须十分谨慎。在没有RAII应用的情况下,如果在内存释放之前就离开指针的作用域,这时候几乎没机会去释放该内存,除非垃圾回收器对其管制,否则我们要面对的将会是内存泄漏。
举个例子来说明下RAII在内存分配方面的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | struct ByteArray { unsigned char* data_; int length_; }; void create_bytearray(ByteArray*, int length); void destroy_bytearray(ByteArray*); void bar() { ByteArray ba; create_bytearray(&ba, 2048); /* 使用 */ /* 如果有异常,Oops */ ... destroy_bytearray(&ba); } |
因此值得注意的是,destroy_bytearray必须在退出作用域前被调用。
然而在复杂的逻辑设计中,程序员往往要花大量的精力以确认所有在该作用域分配的ByteArray得到正确的释放。
相形之下,C++运行机制保证了栈上对象一旦即将离开作用域,其析构函数将被执行,给予了释放资源的时间。注意,在堆分配的对象必须调用delete来结束其生命。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | struct ByteArray { ByteArray():length_(0), data_(0) {} ByteArray(int length) : length_(length) { data_ = new unsigned char [length]; //< 注意这里或许会抛异常 memset (data_, 0, length_); } ~ByteArray() { if (nullptr != data_) delete data_; } unsigned char* data_; int length_; private: ByteArray(const ByteArray&); }; void bar() { ByteArray ba(2048); /* 使用 */ ... } //< 正确地被析构,没有内存泄漏 |
譬如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include void bar() { ByteArray* ba = new ByteArray(2048); std::unique_ptr holder (ba); /* 使用 */ ... } //< 正确地被析构,没有内存泄漏 void foo() { try { bar(); } catch (const char* e) { ... } catch (...) { ... } } |
下面是ByteArray的Ada实现:
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | -- lib.ads with interfaces; with Ada.Finalization; package lib is type uchars is array(positive range<>) of interfaces.unsigned_8; type uchars_p is access uchars; type ByteArray is new Ada.Finalization.Limited_Controlled with private; function Create(length : integer) return ByteArray; private type ByteArray is new Ada.Finalization.Limited_Controlled with record length : integer; data : uchars_p; end record; overriding procedure Initialize (This: in out ByteArray); overriding procedure Finalize (This: in out ByteArray); end lib; -- lib.adb with Ada.Unchecked_Deallocation; package body lib is use Ada.Finalization; function Create(length : integer) return ByteArray is begin if length < 0 then put_line("Create"); return ByteArray'(Limited_Controlled with length => length, data=> new uchars(1..length)); end if; return ByteArray'(Limited_Controlled with length => 0, data=> null); end Create; overriding procedure Initialize (This: in out ByteArray) is begin put_line("Initialize"); this.length := 0; this.data := null; end Initialize; overriding procedure Finalize (This: in out ByteArray) is procedure free is new Ada.Unchecked_Deallocation (uchars, uchars_p); begin put_line("Finalize"); if (this.data /= null) then free(this.data); end if; end Finalize; end lib; -- main.adb with lib; use lib; procedure main is K : ByteArray := Create(10240); C : ByteArray; begin null; end main; |
./main
Create
Initialize
Finalize
Finalize
另一种情况是对I/O资源的处理,当我们不再使用资源时,必须将资源归还给系统。
下面例子来自 wikipedia的RAII条目:
1 2 3 4 5 6 7 8 | void write_to_file (const std::string & message) { static std::mutex mutex; std::lock_guard lock(mutex); std::ofstream file("example.txt"); if (!file.is_open()) throw std::runtime_error("unable to open file"); file << message << std::endl; } |
Pimpl
Pimpl(pointerto implementation),是一种应用十分广泛的技术,它的别名也很多,如Opaque pointer, handle classes等。
wikipedia上已经对其就Ada、C和C++举例,这里不作举例。
个人认为,Pimpl是RAII的延展,籍由RAII对资源的控制,把具体的数据布局和实现从调用者视线内移开,从而简化了API接口,也使得ABI兼容变得有可能,Qt和KDE正是使用Pimpl来维护ABI的一致性,另外也为惰性初始化提供途径,以及隐式共享提供了基础。
我在设计代码时也会考虑使用Pimpl,但不是必然使用,因为Pimpl也会带来副作用,主要有两方面
Pimpl指针导致内存空间开销增大
类型间Pimpl的访问需要较多间接的指针跳转,甚至还用使用friend''来提升访问权限,如以下代码中,Teacher可以访问Student的Context。
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 | // student.h class Student { public: explicit Student(const char* name, int age); ~Student(); private: ///< Pimpl struct Context; Context* const context_; friend class Teacher; }; // student_p.h #include "student.h" struct Student::Context { explicit Context(const char* name, int age) { ... } //< 实质的数据存储在这里 }; // student.cpp #include "student_p.h" Student::Student(const char* name, int age) : context_(new Context(name, age) {} ... |
相关文章推荐
- RAII和垃圾收集(上)
- C++ : 应用 RAII 技术在 Windows 下实现自动释放锁
- 将pimpl习惯用法运用到server的编写。
- RAII技术--获取资源即初始化
- RAII
- C++ pImpl
- PIMPL 模式的实现及应用。
- 强耦合和解耦合的一种情况-pimpl
- C++程序的设计机制3 RAII机制(2)
- C++ 之 RAII
- CStockPropImpl
- C++ 笔记 RAII
- RAII惯用法:C++资源管理的利器
- 【C++自我精讲】基础系列六 PIMPL模式
- Effective C++学习笔记之对RAII思想的思考
- RAII vs. exceptions
- RAII、异常、构造函数是一家人
- C++11 JNI开发中RAII的应用(一)--制作基础工具
- RAII和模拟实现智能指针
- C++中的RAII