码迷,mamicode.com
首页 > 其他好文 > 详细

RAII和unique_ptr

时间:2014-07-22 23:14:13      阅读:501      评论:0      收藏:0      [点我收藏+]

标签:des   style   http   color   使用   os   

RAII

RAII是Resource Acquisition Is Initialization的缩写,是在面向对象(object-oriented)语言中使用的一种编程习惯,主要是用来在C++中处理异常安全资源管理(exception-safe resource management)。

在RAII中,资源的获取和释放和对象的声明周期紧密联系在一起,当对象构造的时候,在构造函数中申请资源( resource allocation),而在对象的析构函数中释放资源(resource deallocation),当析构函数正确执行时,就不会有资源泄露,这让我想起了Linux内核驱动,在编写驱动的过程中,一般也是在init函数中申请资源,然后在exit函数中释放资源,能够避免资源泄露,因此这种思想普遍运用在编程中。

优点

RAII作为一种资源管理的技术主要有以下优点:

  1. 封装性,因为将异常处理的逻辑写到了构造和析构函数中,因此不用调用者关心怎么对资源进行管理。
  2. 异常安装,对于栈对象,当异常发生的时候,异常处理会在离开当前scope的时候,执行对象的析构函数,从而释放内存。
  3. 可定位,能够将资源申请和释放的逻辑集中写在构造和析构中,不会散乱在各个地方。

典型应用

RAII作为一种资源管理技术,主要运用于下面几个地方: 1. 在多线程环境下控制同步锁。在多线程中,为了线程间同步,经常要申请、释放锁,而有时候,经常会出现申请了,但是没有或者由于异常等原因没有释放锁,从而出现死锁。对于这种申请、释放锁的共走,可以交给对象的构造和析构函数,这样可以将申请、释放封装起来,并且保证了异常安全。 2. 文件处理,一般在处理文件的时候,我们都要先open-write/read-close,对于这种固定的结构,完成可以将read-write封装起来。 3. 动态对象的所有权问题。针对动态对象的所有权,C++提供了smart pointer,其中std::unique_ptr针对单拥有权问题,而std::shared_ptr则是针对共享对象。

例子:

#include <string>
#include <mutex>
#include <iostream>
#include <fstream>
#include <stdexcept>

void write_to_file (const std::string & message) {
    // 互斥访问
    static std::mutex mutex;

    // 加锁
    std::lock_guard<std::mutex> lock(mutex);

    // 打开文件
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");

    // 写
    file << message << std::endl;

    // 在离开scope的时候,ofstream在析构函数中关闭文件,
    // lock_guard会在构造中加锁,在析构中解锁
}

GCC对于C的扩展

gnu针对C提供了一种非标准的扩展来支持RAII: "cleanup" variable attribute。下面是一个例子:

static inline void fclosep(FILE **fp) { if (*fp) fclose(*fp); }
#define _cleanup_fclose_ __attribute__((cleanup(fclosep)))
void example_usage() {
  _cleanup_fclose_ FILE *logfile = fopen("logfile.txt", "w+");
  fputs("hello logfile!", logfile);
}

下面对前面提到的unique_ptr进行介绍。

C++11: unique_ptr

unique_ptr是C++11新增的一个特性,作为一种新的smart pointer,主要用于管理只有单一拥有权的动态对象。

基本用法:

std::unique_ptr<foo> p( new foo(42) );

unique_ptr有着智能指针的优点,在析构函数中会自动释放资源。

在C++11之前有auto_ptr用来做动态对象的管理,但是复制auto_ptr会将控制权从右值转移到左值,原先的右值将不再拥有对象的控制权,这和传统的“复制”语义不符,而且由于这种复制语义,导致了auto_ptr不能被用在标准的容器中。在C++11中出现了unique_ptr开解决这个问题。使用unique_ptr的时候,unique_ptr可以存储在容器中,当容器析构的时候unique_ptr指向的对象也被释放。

那C++11是怎么解决不能存在在容器中的问题的呢?通过添加了rvalue reference和move语义。

什么是rvalue reference?

rvalue reference是C++的一个小扩展,rvalue reference允许coder避免逻辑上不必要的复制,并且提供了一个完美的转发功能。主要目的是帮助设计高性能、健壮的库。在A Brief Introduction to Rvalue References中有对rvalue reference的介绍。那为什么要引入rvalue reference,引入rvalue reference解决了什么问题呢?

Lvalues vs rvalues

传递给函数参数的时候,我们可以传值或者传引用。在C中,传引用是通过传递指针,而指针实际上就是传递的一个地址,在C/C++中,内存模型就是申请一个box,然后在这个box中存放数据,而传递的时候就是传递这个box的地址,对于这个box怎么管理从而引申出好多问题。但是不是所有的数据都有地址,譬如某些存放在寄存器中的数据,此时就没有地址了,因此,对于有地址的数据我们叫做lvalues,而没地址的数据叫rvalues。那实际中有哪些rvalues,譬如函数的返回值,数值计算式等。

下面是一个例子:

void f(int *pi);
int g();

f(&g()); //error
f(&(1+1)); // error

上面这个是无法通过的,因为传入的参数是右值,没有地址,一个简单的修改如下:

int temp1 = g();
f(&temp1); 
int temp2 = 1 + 1;
f(&temp2;

那为什么编译器不帮我们创建临时变量,然后不再报错呢?

让我们想下,一般当参数是地址的时候,我们在函数内部是希望改变指向的对象的值,如:

void f(int *pi){ ++*pi; }

此时我们如果传入一个临时变量,对于这个函数内部的操作就变的没有意义了。因此,此时编译器就会报错,不允许将rvalue的地址传入。

Reference

在C++中,除了指针外,还加入了引用,于是,上面的代码可以重写如下:

void f(int &pi){ pi++; }
int g();

f(g()); //error
f((1+1)); // error

此时还是无法得到rvalue的引用(reference),但是有时候传递引用并不是为了改变其值,对于一些大的数据,传递引用可以减少开销,此时并不对lvalue和rvalue有要求。因此,编译器因该能够允许传递rvalue的reference。

Const reference

当我们不会改变传递进来的reference的值时,则传递进来lvalue或者rvalue都是可以的,因此,上面的代码如果改成下面的:

void f(const int &pi){ pi++; }
int g();

f(g()); //ok
f((1+1)); // ok

此时看起来一切都挺好的,但是引入ato_ptr后,就会带来一系列的问题。

现在总结下reference和lvalue和rvalue的关系:

  1. reference可以绑定到lvalues
  2. const reference可以绑定到lvalues和rvalues,但是不允许改变源值

上面看上去都挺好的,除了不能将reference绑定到rvalues,并且修改,但是谁又想要改变一个临时的值呢?

auto_ptr

auto_ptr是智能指针中的一员,用来自动释放指向的内存块,auto_ptr有value的语义,可以传递,在栈上创建,或者是其他数据的长成员,在传递给函数的时候是通过值传递,但是当auto_ptr进行值传递的时候,其指向的数据并不复制,这又像reference的行为,同时可以对auto_ptr使用*和->,就像指针一样。

考虑下面的代码:

auto_ptr<int> create() {
   return auto_ptr<int>(new int(42));
 }

 auto_ptr<int> ap = create();

考虑上面的代码,在create()内部,当结束的时候,刚离开scope的时候,如果不做什么,编译器会调用auto_ptr的析构函数,从而释放内存;函数create返回的是一个rvalue。

对于第一点,调用析构函数,我们希望将其赋给ap,此时因该调用拷贝构造函数,而对于右值有下面的两个问题:

  1. 如果我们定义拷贝构造函数的source为const reference,则此时无法修改source,将拥有权转移。
  2. 如果定义非const的reference,此时不能够绑定到右值。

rvalue reference

对于上面的问题,我们需要的是rvalue reference,能够绑定到rvalue,并且修改他。于是针对auto_ptr不能复制构造右值,出现了unique_ptrunique_ptr有下面的复制构造函数:

unique_ptr::unique_ptr(unique_ptr && src)

rvalue reference能够同时绑定到lvalue和rvalue,并且不阻止改变值。

why auto_ptr can‘t store auto_ptr objects in most containers

auto_ptr还有一点问题是,不能够存储在大多数容器中,为什么呢?考虑下面的一个例子:

 auto_ptr<Foo> pSrc(new Foo);
 auto_ptr<Foo> pDest = pSrc; // 看起来像像拷贝,但是不具备拷贝的语义,会将源中指针置空    pSrc->method(); // 运行时错误,因为此时pSrc中指针为空

而在容器中,会有临时变量,一旦拷贝,则容器中的值将不再有所有权,这是严重的错误。

但是有时候我们又想要将控制权进行转移,此时我们应该明确的告知说我们现在要转移控制权了,因此不要再去使用原先的源了。

此处我们总结下rvalue和lvalue的不同,rvalue会在赋值后消失,而lvalue则保持不变。我们看到上面的unique_ptr的复制构造函数:

unique_ptr::unique_ptr(unique_ptr && src)

这会同时绑定rvalue好lvalue,因此我们需要下面的一个重载函数:

unique_ptr::unique_ptr(unique_ptr & src)

重载rvalue,此时我们要做的就是将rvalue的构造私有化,现在unique_ptr只保持转移控制权的语义了,而当我们想要将一个lvalue转移到unique_ptr的时候,此时使用move函数,

unique_ptr pSrc(new Foo);
unique_ptr pDest = move(pSrc);

明确的将lvalue变为rvalue,move作用就是将一个lvalue转成rvalue,

template <class T>
 typename remove_reference<T>::type&& move(T&& t) {
    return t;
 }

下面我们接着介绍unique_ptr,unique_ptr中unique在此处是指什么呢?此处unique表示,当你创建一个对象的时候,只会有一份拷贝,只会有一个指针。

平时我们使用指针的时候,经常会碰到下面的情形:

foo *p = new foo("useful object");
make_use( p );

首先创建一个对象,然后将指针传递给make_use,此时在make_use会对指针做什么呢?make_use会拷贝指针,稍后使用嘛?或者make_use会释放指针吗?我们无法很好的回答这些问题,因为C++不能保证make_use对指针的使用规范,我们只能通过检查代码来保证不会错误的时候指针。这些问题可以通过unique_ptr解决,只保证一份拷贝,不会有其他拷贝。

现在我们使用指针的时候,将其放入到unique_ptr,保证拥有权是唯一的,不会隐式转移,要转移时通过明确的move函数将其转成rvalue,进行转移,此处,使用unique_ptr后,会带来的一点不同是,我们将会传递引用,将其当做值来传递,如下面:

void inc_baz( std::unique_ptr<foo> &p )
{
    p->baz++;
}

总结

本文首先介绍了RAII,即Resource Acquisition Is Initialization一种资源安全管理的方式,然后引出了smart point,随后又介绍了auto_ptr,早期对RAII的一种尝试,后来由于其存在的一些问题,通过介绍lvalue、rvalue的概念,最后在C++11中给出了解决方案,unique_ptr,并给出move语义,明确的将左值转换为rvalue,进行控制权的转移。

参考:

http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

http://www.drdobbs.com/cpp/c11-uniqueptr/240002708

http://bartoszmilewski.com/2008/10/18/who-ordered-rvalue-references-part-1/

RAII和unique_ptr,码迷,mamicode.com

RAII和unique_ptr

标签:des   style   http   color   使用   os   

原文地址:http://www.cnblogs.com/shengjianjun/p/3699920.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!