C++11之智能指针

为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer)。

智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈上的变量自动被销毁,智能指针内部保存的内存也就被释放掉了(除非将智能指针保存起来)。

C++11提供了三种智能指针:std::shared_ptr, std::unique_ptr,std::weak_ptr,使用时需添加头文件 < memory > 。

1.std::unique_ptr

unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,即一个对象资源只能同时被一个unique_ptr指向。不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。

1.1 初始化方式

//通过new运算符或者普通指针
unique_ptr up(new Object());
//或者
Object *pInv = new Object();
unique_ptr up1(pInv);

//通过make_unique
auto pInv = make_unique();

//通过move()函数
unique_ptr up1 = std::move(up); 
  

1.2 获取原始指针

unique_ptr p(new Object());
Object* pObj = p.get();

unique_ptr<int[]> pi(new int[5]{ 1, 2, 3, 4, 5 });
//如果管理的是一个动态数组,那么返回数组的头结点指针
int * pti = pi.get(); 
  

1.3 无法进行复制构造和赋值操作

unique_ptr没有copy构造函数,不支持普通的拷贝和赋值操作。

unique_ptr up(new Object()); //ok

unique_ptr up1(up);              //error, can not be copy

unique_ptr up2 = up;            //error, can not be assigned

但是,unique_ptr可以作为函数的返回值:

unique_ptr GetPtr();        //function getthe unique pointer

unique_ptr pInv = GetPtr(); // ok 
  

1.4 可以进行移动构造和移动赋值操作

unique_ptr<int> pInt(new int(5));
unique_ptr<int> pInt2 = std::move(pInt);    // 转移所有权
//cout << *pInt << endl; // 出错,pInt为空
cout << *pInt2 << endl;
unique_ptr<int> pInt3(std::move(pInt2));

1.5 自定义释放器

//使用lambda表达式
auto delete_Investment = [](Investment* pInv)

{
    pInv->getObjectType();
    delete pInv;
};

unique_ptrdecltype(delete_Investment)> pInvest(nullptr,delete_Investment);

//或者也可以使用函数指针
void deleteInv(Investment* pInv) {}
std::unique_ptrvoid(*)(Investment*)>ptr(nullptr,deleteInv) ;

1.6 使用场景

1. 为动态申请的资源提供异常安全保证

void Func()
{
    int *p = new int(5);

    // ...(可能会抛出异常)

    delete p;
}

当我们动态申请内存后,有可能我们接下来的代码由于抛出异常或者提前退出(if语句)而没有执行delete操作。

解决的方法是使用unique_ptr来管理动态内存,只要unique_ptr指针创建成功,其析构函数都会被调用。确保动态资源被释放。

void Func()
{
    unique_ptr<int> p(new int(5));

    // ...(可能会抛出异常)
}

2. 返回函数内动态申请资源的所有权

unique_ptr<int> Func(int p)
{
    unique_ptr<int> pInt(new int(p));
    return pInt;    // 返回unique_ptr
}

int main() {
    int p = 5;
    unique_ptr<int> ret = Func(p);
    cout << *ret << endl;
    // 函数结束后,自动释放资源
}

3. 在容器中保存指针

vectorint>> vec;
unique_ptr<int> p(new int(5));
vec.push_back(std::move(p));    // 使用移动语义

4. 管理动态数组

unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5});
p[0] = 0;   // 重载了operator[]

2.std::shared_ptr

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁。

2.1 初始化

class Person
{
public:
    Person(int v) {
        value = v;
    }
    ~Person() { }

    int value;
};

std::shared_ptr p1(new Person(1));// Person(1)的引用计数为1

std::shared_ptr p2 = std::make_shared(2);

p1.reset(new Person(3));// 首先生成新对象,然后引用计数减1,引用计数为0,故析构Person(1)
                        // 最后将新对象的指针交给智能指针

std::shared_ptr p3 = p1;//现在p1和p3同时指向Person(3),Person(3)的引用计数为2

p1.reset();//Person(3)的引用计数为1
p3.reset();//Person(3)的引用计数为0,析构Person(3)

  reset()有两种操作。当智能指针中有值的时候,调用reset()会使引用计数减1.当调用reset(new xxx())重新赋值时,智能指针首先是生成新对象,然后将就对象的引用计数减1(当然,如果发现引用计数为0时,则析构旧对象),然后将新对象的指针交给智能指针保管。

2.2 获取原始指针

std::shared_ptr<int> p4(new int(5));
int *pInt = p4.get();

2.3 自定义释放器

智能指针可以指定删除器,当智能指针的引用计数为0时,自动调用指定的删除器来释放内存。std::shared_ptr可以指定删除器的一个原因是其默认删除器不支持数组对象,这一点需要注意。

template< typename T >
struct array_deleter
{     
    void operator ()(T const * p)
    {
        delete[] p;
    }
};

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

此时,shared_ptr可正确的调用delete[]。

在C++11中,可以使用 std::default_delete代替上面自己写的array_deleter:

std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());

也可以使用一下的lambda表达式来自定义删除函数

std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

实际上,除非需要共享目标,否则unique_ptr更适合使用数

2.4 使用shared_ptr需要注意的问题

1. 不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁,如下所示

int *p5 = new int;
std::shared_ptr<int> p6(p5);
std::shared_ptr<int> p7(p5);// logic error

2. 不要在函数实参中创建shared_ptr。

因为C++的函数参数的计算顺序在不同的编译器下是不同的。正确的做法是先创建好,然后再传入。

function(shared_ptr<int>(new int), g()); //error

3. 禁止通过shared_from_this()返回this指针,这样做可能也会造成二次析构

4. 避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏.

解决方法是AStruct或BStruct改为weak_ptr。

#include 
#include 
#include 
using namespace std;

class ClassB;

class ClassA
{
public:
    ClassA() { cout << "ClassA Constructor..." << endl; }
    ~ClassA() { cout << "ClassA Destructor..." << endl; }
    shared_ptr pb;  // 在A中引用B
};

class ClassB
{
public:
    ClassB() { cout << "ClassB Constructor..." << endl; }
    ~ClassB() { cout << "ClassB Destructor..." << endl; }
    shared_ptr pa;  // 在B中引用A
};

int main() {
    shared_ptr spa = make_shared();
    shared_ptr spb = make_shared();
    spa->pb = spb;
    spb->pa = spa;
    // 不能正常释放spa和spb
}

3. std::weak_ptr

weak_ptr是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象.进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段. weak_ptr设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.

  定义在 memory 文件中(非memory.h), 命名空间为 std.

3.1 初始化

当我们创建一个weak_ptr时,需要用一个shared_ptr实例来初始化weak_ptr,weak_ptr的创建并不会影响shared_ptr的引用计数值。

shared_ptr<int> sp(new int(5));
weak_ptr<int> wp(sp);

3.2 使用

既然weak_ptr并不改变其所共享的shared_ptr实例的引用计数,那就可能存在weak_ptr指向的对象被释放掉这种情况。这时,我们就不能使用weak_ptr直接访问对象。那么我们如何判断weak_ptr指向对象是否存在呢?C++中提供了lock函数来实现该功能。如果对象存在,lock()函数返回一个指向共享对象的shared_ptr,否则返回一个空shared_ptr。

class A
{
public:
    A() : a(3) { cout << "A Constructor..." << endl; }
    ~A() { cout << "A Destructor..." << endl; }

    int a;
};

int main() {
    shared_ptr sp(new A());
    weak_ptr wp(sp);
    //sp.reset();

    if (shared_ptr pa = wp.lock())
    {
        cout << pa->a << endl;
    }
    else
    {
        cout << "wp指向对象为空" << endl;
    }
}

除此之外,weak_ptr还提供了expired()函数来判断所指对象是否已经被销毁。

class A
{
public:
    A() : a(3) { cout << "A Constructor..." << endl; }
    ~A() { cout << "A Destructor..." << endl; }

    int a;
};

int main() {
    shared_ptr sp(new A());
    weak_ptr wp(sp);
    sp.reset(); // 此时sp被销毁
    cout << wp.expired() << endl;  // true表示已被销毁,否则为false
}

3.3 解决shared_ptr的循环引用问题

class ClassB;

class ClassA
{
public:
    ClassA() { cout << "ClassA Constructor..." << endl; }
    ~ClassA() { cout << "ClassA Destructor..." << endl; }
    weak_ptr pb;  // 在A中引用B
};

class ClassB
{
public:
    ClassB() { cout << "ClassB Constructor..." << endl; }
    ~ClassB() { cout << "ClassB Destructor..." << endl; }
    weak_ptr pa;  // 在B中引用A
};

int main() {
    shared_ptr spa = make_shared();
    shared_ptr spb = make_shared();
    spa->pb = spb;
    spb->pa = spa;
    // 可以正常释放spa和spb
}

参考博客:
https://www.cnblogs.com/DswCnblog/p/5628195.html
https://blog.csdn.net/jxianxu/article/details/72858885
https://www.cnblogs.com/jiayayao/archive/2016/12/03/6128877.html
http://www.cnblogs.com/darkknightzh/p/5462363.html
https://blog.csdn.net/Xiejingfa/article/details/50772571

你可能感兴趣的:(c++)