(原创)智能指针拾遗

本文将介绍智能指针用法的一些平时可能没注意的细节(关于智能指针的基本用法能够参考前面的博文)。html

1.unique_ptr和shared_ptr在构造上的一点差别

  unique_ptr支持动态数组,而shared_ptr不能直接支持动态数组。std::unique_ptr<int []> ptr(new int[10]);合法,而std::shared_ptr<int []> ptr(new int[10]);是不合法的。
  若是经过std::shared_ptr来构造动态数组,则须要显式指定删除器,好比下面的代码:c++

  std::shared_ptr<int> p(new int[10], [](int* p){delete[] p;}); //指定delete[]数组

  也能够用std::default_delete做为删除器:安全

  std::shared_ptr<int> p(new int[10], std::default_delete<int[]>);函数

  咱们能够封装一个make_shared_array方法让shared_ptr支持数组:测试

template<typename T> 
shared_ptr<T> make_shared_array(size_t size)
{
   return shared_ptr<T>(new T[size], default_delete<T[]>());
}

测试代码:this

std::shared_ptr<int> p = make_shared_array<int>(10);
std::shared_ptr<char> p = make_shared_array<char>(10);

  unique_ptr缺乏一个相似于make_shared的make_unique方法,不过在c++14中会增长make_unique方法。其实要实现一个make_unique方法是比较简单的:spa

//支持普通指针
template<class T, class... Args> inline
typename enable_if<!is_array<T>::value, unique_ptr<T> >::type make_unique(Args&&... args)
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

//支持动态数组
template<class T> inline
typename enable_if<is_array<T>::value && extent<T>::value==0, unique_ptr<T> >::type 

make_unique(size_t size)
{
    typedef typename remove_extent<T>::type U;
    return unique_ptr<T>(new U[size]());
}

//过滤掉定长数组的状况
template<class T, class... Args>
typename enable_if<extent<T>::value != 0, void>::type make_unique(Args&&...) = delete;

  实现思路很简单,若是不是数组则直接建立unique_ptr,若是是数组的话,先判断是否为定长数组,若是为定长数组则编译不经过;非定长数组时,获取数组中的元素类型,再根据入参size建立动态数组的unique_ptr。extent<T>::value用来获取数组的长度,若是获取值为0,则不到说明不是定长数组。指针

2.shared_ptr和unique_ptr指定删除器方式的一点差别

  unique_ptr指定删除器和std:: shared_ptr是有差异的,好比下面的写法:
std:: shared_ptr<int> ptr(new int(1), [](int*p){delete p;}); //正确
std::unique_ptr<int> ptr(new int(1), [](int*p){delete p;}); //错误c++11


  std::unique_ptr指定删除器的时候须要肯定删除器的类型,因此不能直接像shared_ptr指定删除器,能够这样写:
std::unique_ptr<int, void(*)(int*)> ptr(new int(1), [](int*p){delete p;});

  上面这种写法在lambda没有捕获变量的状况下是正确的,若是捕获了变量则会编译报错:
std::unique_ptr<int, void(*)(int*)> ptr(new int(1), [&](int*p){delete p;}); //错误,由于捕获了变量

  为何lambda捕获了变量做为unique_ptr就会报错呢,由于lambda在没有捕获变量的状况下是能够直接转换为函数指针的,捕获了就不能转换为函数指针。

  若是但愿unique_ptr的删除器支持lambda,能够这样写:

std::unique_ptr<int, std::function<void(int*)>> ptr(new int(1), [&](int*p){delete p;});

  咱们还能够自定义unique_ptr的删除器,好比下面的代码:

#include <memory>
#include <functional>
using namespace std;

struct MyDeleter
{
    void operator()(int*p)
    {
        cout<<"delete"<<endl;
        delete p;
    }
};

int main() {
    std::unique_ptr<int, MyDeleter> p(new int(1));
    return 0;
}

3.经过智能指针管理第三方库分配的内存

  智能指针能够很方便的管理当前程序库动态分配的内存,还能够用来管理第三方库分配的内存。第三方库分配的内存通常须要经过第三方库提供的释放接口才能释放,因为第三方库返回出来的指针通常都是原始指针,若是用完以后没有调用第三方库的释放接口,就很容易形成内存泄露。好比下面的代码:

void* p = GetHandle()->Create();
//do something…
GetHandle()->Release(p);

  这段代码其实是不安全的,在使用第三方库分配的内存过程当中,可能忘记调用Release接口,还有可能中间不当心返回了,还有可能中间发生了异常,致使没法调用Release接口,这时用智能指针去管理第三方库的内存就很合适了,只要出了做用域内存就会自动释放,不用显式去调用释放接口了,不用担忧中途返回或者发生异常致使没法调用释放接口的问题。

void* p = GetHandle()->Create();
std::shared_ptr<void> sp(p, [this](void*p){ GetHandle()->Release(p);});

  上面这段代码就能够保证任什么时候候都能正确释放第三方库分配的内存。虽然能解决问题,但仍是有些繁琐,由于每一个第三方库分配内存的地方都要调用这段代码,比较繁琐,咱们能够将这段代码提炼出来做为一个公共函数,简化调用。

std::shared_ptr<void>  Guard(void* p)
{
    return std::shared_ptr<void> sp(p, [this](void*p){ GetHandle()->Release(p);});
}
void* p = GetHandle()->Create();
auto sp = Guard(p);
//do something…

  上面的代码经过Guard函数作了简化,用起来比较方便,但仍然不够安全,由于有可能使用者可能会这样写:

void* p = GetHandle()->Create();
Guard(p); //危险,这句结束以后p就被释放了
//do something…

  这样写是有问题的,会致使访问野指针,由于Guard(p);是一个右值,若是不赋值给一个指针的话,Guard(p);这句结束以后,就会释放,致使p提早释放了,后面就会访问野指针的内容。auto sp = Guard(p);须要一个赋值操做,忘记赋值就会致使指针提早释放,这种写法仍然不够安全。咱们能够定义一个宏来解决这个问题:

#define GUARD(P) std::shared_ptr<void> p##p(p, [](void*p){ GetHandle()->Release(p);});

void* p = GetHandle()->Create();
GUARD(p); //安全

  也能够用unique_ptr来管理第三方的内存:

#define GUARD(P) std::unique_ptr<void, void(*)(int*)> p##p(p, [](void*p){ GetHandle()->Release(p);});

  经过宏定义方式咱们能够避免智能指针忘记赋值,即方便又安全。

c++11 boost技术交流群:296561497,欢迎你们来交流技术。

相关文章
相关标签/搜索