[C++11札记] :智能指针

原创 2018年02月08日 09:34:11

内存问题一直是C/C++程序员面临的重大挑战,就语言层面而言,主要问题有:

  • 野指针:一些内存单元已被释放,之前指向它的指针却还在被使用。这些内存有可能被系统重新分配给程序使用,从而导致了无法预测的错误。
  • 重复释放:程序试图去释放已经被释放过的内存单元,或者释放已经被重新分配过的内存单元,导致重复释放错误。
  • 内存泄漏:不再需要使用的内存单元如果没有被释放就会导致内存泄漏。如果程序不断地重复进行这类操作,将会导致内存占用剧增,甚至导致系统无可用内存,严重影响系统的运行。

随着多线程程序的出现和广泛使用,内存问题更加突出。为了让程序员从内存管理细节中解放出来,越来越多的程序及程序库采用了智能指针(smart pointer)。

智能指针

智能指针泛指一类原生指针(raw pointer)的封装,它的行为非常类似于原生指针,同时又可以自动化地实现资源管理(比如对象的自动析构)。智能指针使用广泛,几乎在所有的大型工程中都可以看到它的身影,比如boost、Android、WebKit、Chromium中都自行设计了智能指针。虽然这些开源项目中智能指针的实现各有不同,但其基本原理都是一样的:

  • 在C++语言中,智能指针对象作为栈上分配的自动变量存在,比如局部变量、类的成员变量,在代码执行上下文退出其作用域时被自动析构。
  • 智能指针的析构函数中一般包含封装指针对象的delete操作,从而间接实现了被封装对象的自动析构。

下面的代码展示智能指针的一般实现:

template <typename T>
class SmartPtr {
public:
    typedef T ValueType;
    typedef ValueType *PtrType;
    SmartPtr() : m_ptr(NULL) {}
    SmartPtr(PtrType ptr) : m_ptr(ptr) {}
    ~SmartPtr() { if (m_ptr) delete m_ptr; }
    SmartPtr(const SmartPtr<T>& o);
    template<typename U> SmartPtr(const SmartPtr<U>& o);
    template<typename U> SmartPtr& operator=(const SmartPtr<U>& o);
    // 指针运算
    ValueType& operator*() const { return *m_ptr; }
    PtrType operator->() const { return m_ptr; }
    // 逻辑运算符重载
    bool operator!() const { return !m_ptr; }
    // 转换为raw ptr
    operator PtrType() { return m_ptr; }
private:
    PtrType m_ptr;
};

auto_ptr

在C++ 98中,智能指针通过一个模板类型auto_ptr来实现,程序员只需将new操作返回的指针作为auto_ptr的初始值即可,不用再显式的调用delete。比如:

auto_ptr(new int);

这在一定程度上避免了堆内存忘记释放而造成的内存泄漏。但是auto_ptr存在很明显的缺点,它采取了独占所有权模式,这样两个相同类型的指针不能同时指向相同的资源。比如下面的代码:

// C++ program to illustrate the use of auto_ptr
#include<iostream>
#include<memory>
using namespace std;

class A
{
public:
    void show() {  cout << "A::show()" << endl; }
};

int main()
{
    // p1 is an auto_ptr of type A
    auto_ptr<A> p1(new A);
    p1 -> show();

    // returns the memory address of p1
    cout << p1.get() << endl;

    // copy constructor called, this makes p1 empty.
    auto_ptr <A> p2(p1);
    p2 -> show();

    // p1 is empty now
    cout << p1.get() << endl;

    // p1 gets copied in p2
    cout<< p2.get() << endl;

    return 0;
}

输出为:

A::show()
0x1b42c20
A::show()
0          
0x1b42c20

形象的用图形表示如下:

image

auto_ptr的拷贝构造函数和赋值操作符实际上并不复制已存储的指针,而是传递它,使第一个auto_ptr对象为空。由于auto_ptr不支持拷贝语义,所以不能用于STL容器,它的另一个缺点就是不能调用delete[],而无法用于数组。

介于auto_ptr有上述的缺点,所以在C++11标准中被废弃了,取而代之的是unique_ptr, shared_ptr及weak_ptr。

unique_ptr

C++11中unique_ptr是用来取代auto_ptr的,就像其名字所表明的,它与所指对象的内存紧密绑定,不能与其他unique_ptr类型的指针对象共享所指对象的内存。比如下面的代码是无法编译通过的:

unique_ptr<int> up1(new int(11));
unique_ptr<int> up2 = up1;  // 不能通过编译

相比auto_ptr,这可以避免程序中误用拷贝。当然,如果程序员确实希望使用auto_ptr那样的转移所有权操作,可以借助std::move来完成:

// Works, resource now stored in ptr2
unique_ptr<A> ptr2 = move(ptr1); 

值得注意的是,如果unique_ptr作为函数的返回值,下面的代码会自动使用move语义而不会出现编译错误:

unique_ptr<A> fun()
{
    unique_ptr<A> ptr(new A);

    /* ...
       ... */

    return ptr;
}

此外unique_ptr还增加了对数组的支持,所以在代码中应该使用unique_ptr而不应该使用废弃了的auto_ptr。

shared_ptr

在有的情形下,程序可能需要共享“拥有”同一个堆分配对象的内存,这个时候shared_ptr就可以派上用场。shared_ptr采用引用计数所有权模型,它与shared_ptr的所有副本合作维护其包含的指针的引用计数。每当一个新的指针指向资源时,计数器就会增加,当shared_ptr析构时,计数器就会递减。引用计数大于零时,shared_ptr包含的原始指针不会被销毁,直到引用计数递减到零才会释放。

所以,当我们要分配一个原始指针给多个所有者时,应该使用shared_ptr。

// C++ program to demonstrate shared_ptr
#include<iostream>
#include<memory>
using namespace std;

class A
{
public:
    void show()
    {
        cout<<"A::show()"<<endl;
    }
};

int main()
{
    shared_ptr<A> p1 (new A);
    cout << p1.get() << endl;
    p1->show();
    shared_ptr<A> p2 (p1);
    p2->show();
    cout << p1.get() << endl;
    cout << p2.get() << endl;

    // Returns the number of shared_ptr objects
    //referring to the same managed object.
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;

    // Relinquishes ownership of p1 on the object
    //and pointer becomes NULL
    p1.reset();
    cout << p1.get() << endl;
    cout << p2.use_count() << endl;
    cout << p2.get() << endl;

    return 0;
}

输出:

0x1c41c20
A::show()
A::show()
0x1c41c20
0x1c41c20
2
2
0          // NULL
1
0x1c41c20

weak_ptr

在C++11标准中,除了unique_ptr和shared_ptr,智能指针还包括了weak_ptr这个类模板。weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存。使用weak_ptr成员函数lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回空指针。

为什么会引入weak_ptr呢?它是为了解决shared_ptr循环依赖导致的内存问题而引入的。让我们考虑一个场景,有两个类A和B,都包含指向对方类的shared_ptr指针,这样A指向B,B指向A,引用计数永远不会达到零,两个对象也永远不会被删除。

image

引入weak_ptr后,声明weak_ptr的类不共享所有权,但是它可以通过lock方法访问这些对象。通过weak_ptr,可以打破A和B之间的循环依赖。

image

总结

虽然智能指针能帮助用户进行有效的堆内存管理,但是它还是需要程序员显式地声明智能指针。此外我们需要小心地使用shared_ptr,避免循环依赖导致内存无法释放。weak_ptr提供了解决循环依赖的途径,但决定何时使用shared_ptr,何时使用weak_ptr仍然是程序员的职责。

参考

  1. 深入理解C++11: C++11新特性解析与应用,p163 ~ p173
  2. 深入理解Android: WebKit卷, p40 ~ p45
  3. auto_ptr, unique_ptr, shared_ptr and weak_ptr

image

版权声明:本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接http://blog.csdn.net/mogoweb

boost 1.33 仍然没有network library

boost 1.33 出来了, 仍然没有我期待很久的network library. boost中, 有不少是语法甜头, 或者接近语法甜头, 例如lambda, bind, assign, tuple...
  • ilovevc
  • ilovevc
  • 2005年08月17日 11:39
  • 2514

boost源码剖析之:多重回调机制signal(上)

boost源码剖析之:多重回调机制signal(上) 刘未鹏C++的罗浮宫(http://blog.csdn.net/pongba) boost库固然是技术的宝库,却更是思想的宝库。大多数程序员都知道...
  • pongba
  • pongba
  • 2007年04月11日 17:50
  • 31335

c++11智能指针解析——揭开底层面纱,完整理解智能指针

昨天跟同事小小的研究了下关于不同平台下,起因是遇到了一个坑,vs上没有问题,在安卓上却崩溃了。找了半天后发现是c++字节补齐问题,期间包括使用#pragma pack(1)来限定字节对齐方式等各种条件...
  • zy19940906
  • zy19940906
  • 2016年01月07日 17:00
  • 11025

c++11 智能指针删除器

其实很简单自己上代码: #include #include #include #include #include using namespace std; struct struct...
  • u012592081
  • u012592081
  • 2016年07月22日 18:24
  • 498

C++11新特性之智能指针

这一节将从用法上、内存存储上以及生存周期上,对unique_ptr, shared_ptr和weak_ptr做一个深入剖析。unique_ptr   不共享它的指针。它无法复制到其他 unique...
  • u013184159
  • u013184159
  • 2016年04月18日 10:57
  • 6540

C++11及C++14标准的智能指针

智能指针这个概念经常会碰见,而且面试的时候太经常会被问到了,特来总结一下。 C++11的智能指针及其背景 auto_ptr的坑点 C++11之前的智能指针是auto_ptr,一开始它的出现...
  • haolexiao
  • haolexiao
  • 2017年03月18日 23:55
  • 1730

boost log库 使用九

boost log立刻写日志
  • sheismylife
  • sheismylife
  • 2013年12月28日 00:44
  • 5134

C++11智能指针的选择使用

建立所有权概念,对于特定的对象,只有一个zhi'neng指针可以拥有它,
  • hanbingfengying
  • hanbingfengying
  • 2014年06月07日 17:23
  • 913

C++11智能指针之使用shared_ptr实现多态

指针除了管理内存之外,在C++中还有一个重要的功能就是实现多态。 代码很简单,还是使用虚函数。与原生指针并没有什么区别: #include #include using namespace std...
  • wks19891215
  • wks19891215
  • 2016年03月27日 13:37
  • 2710

c++ 11 中的智能指针

l  unique_ptr :不允许多个指针共享资源,可以用标准库中的move函数转移指针 l  shared_ptr :多个指针共享资源 l  weak_ptr :可复制sh...
  • susser43
  • susser43
  • 2015年05月18日 22:51
  • 624
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:[C++11札记] :智能指针
举报原因:
原因补充:

(最多只允许输入30个字)