转 PIMPL

RAII 与Pimpl 源地址http://tech.uc.cn/?p=851程序员

RAIIsegmentfault

RAII是Bjarne Stroustrup教授用于解决资源分配而发明的技术,资源获取即初始化。函数

RAII是C++的构造机制的直接使用,即利用构造函数分配资源,利用析构函数来回收资源。布局

咱们知道,在C/C++语言中,对动态分配的内存的处理必须十分谨慎。在没有RAII应用的状况下,若是在内存释放以前就离开指针的做用域,这时候几乎没机会去释放该内存,除非垃圾回收器对其管制,不然咱们要面对的将会是内存泄漏。性能

举个例子来讲明下RAII在内存分配方面的使用。
请输入图片描述this

这是典型的C风格代码,没有应用RAII。
所以值得注意的是,destroy_bytearray必须在退出做用域前被调用。
然而在复杂的逻辑设计中,程序员每每要花大量的精力以确认全部在该做用域分配的ByteArray获得正确的释放。spa

相形之下,C++运行机制保证了栈上对象一旦即将离开做用域,其析构函数将被执行,给予了释放资源的时间。注意,在堆分配的对象必须调用delete来结束其生命。
请输入图片描述设计

C++11 STL中的std::unique_ptr可用于控制做用域中的动态分配的对象。
譬如:
请输入图片描述指针

函数bar()只是增长了一行,但强壮了不少,函数bar()执行完或者有异常抛出时,holder总会被析构,从而ba或被delete。code

下面是ByteArray的Ada实现:

-- lib.ads

    withinterfaces;

    withAda.Finalization;

    packagelib is

        typeuchars isarray(positive range<>)ofinterfaces.unsigned_8;

        typeuchars_p isaccessuchars;

        typeByteArray isnewAda.Finalization.Limited_Controlled withprivate;

        functionCreate(length:integer)returnByteArray;

    private

        typeByteArray isnewAda.Finalization.Limited_Controlled withrecord

            length:integer;

            data:uchars_p;

        endrecord;

        overriding

        procedureInitialize(This:inoutByteArray);

        overriding

        procedureFinalize(This:inoutByteArray);

    endlib;



    -- lib.adb

    withAda.Unchecked_Deallocation;

    packagebodylib is

        useAda.Finalization;

        functionCreate(length:integer)returnByteArray is

        begin

            iflength<0then

                put_line("Create");

                returnByteArray'(Limited_Controlled with length => length,

                       data=> new uchars(1..length));

            end if;

            return ByteArray'(Limited_Controlled withlength=>0,data=>null);

        endCreate;

        overriding

        procedureInitialize(This:inoutByteArray)is

        begin

            put_line("Initialize");

            this.length:=0;

            this.data:=null;

        endInitialize;

        overriding

        procedureFinalize(This:inoutByteArray)is

            procedurefree isnewAda.Unchecked_Deallocation(uchars,uchars_p);

        begin

            put_line("Finalize");

            if(this.data/=null)then

                free(this.data);

            endif;

        endFinalize;

    endlib;



    -- main.adb

    withlib;

    uselib;

    proceduremain is

        K:ByteArray:=Create(10240);

        C:ByteArray;

    begin

        null;

endmain;

– 输出以下
./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

Pimpl(pointer to implementation),是一种应用十分普遍的技术,它的别名也不少,如Opaque pointer, handle classes等。

wikipedia上已经对其就Ada、C和C++举例,这里不做举例。
我的认为,Pimpl是RAII的延展,籍由RAII对资源的控制,把具体的数据布局和实现从调用者视线内移开,从而简化了API接口,也使得ABI兼容变得有可能,Qt和KDE正是使用Pimpl来维护API的一致性,另外也为惰性初始化提供途径,以及隐式共享提供了基础。

我在设计代码时也会考虑使用Pimpl,但不是必然使用,由于Pimpl也会带来反作用,主要有两方面

  • Pimpl指针致使内存空间开销增大

  • 类型间Pimpl的访问须要较多间接的指针跳转,甚至还用使用friend''来提高访问权限,如如下代码中,Teacher能够访问Student的Context。
    请输入图片描述

尽管如此,我我的仍是在面向开发应用的接口中会尽可能使用Pimpl来维护API和ABI的一致性,除非Pimpl会引发显著的性能降低。

相关文章
相关标签/搜索