c++11之智能指针

(一)共享智能指针—shared_ptr

1、智能指针介绍

        智能指针是存储指向动态分配(堆)对象指针类。用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

        C++11 中提供了三种智能指针,使用这些智能指针时需要引用头文件 <memory>:

  1. std::shared_ptr:共享的智能指针
  2. std::unique_ptr:独占的智能指针
  3. std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的。

2、shared_ptr 的初始化

        共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr 是一个模板类,如果要进行初始化有三种方式:

  1. 通过构造函数;
  2. std::make_shared 辅助函数;
  3. reset 方法

        共享智能指针对象初始化完毕之后指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存,可以使用共享智能指针提供的一个成员函数 use_count,函数原型如下:

// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;

通过构造函数初始化

// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);
#include <iostream>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; // 1

    // 使用智能指针管理一块字符数组对应的堆内存
    shared_ptr<char> ptr2(new char[12]);
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; // 1

    // 创建智能指针对象, 不管理任何内存
    shared_ptr<int> ptr3;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;// 0

    // 创建智能指针对象, 初始化为空
    shared_ptr<int> ptr4(nullptr);
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; // 0

    return 0;
}

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数 + 1,如果智能指针没有被初始化或者被初始化为 nullptr 空指针引用计数不会 + 1。另外,不要使用一个原始指针初始化多个 shared_ptr。

例如:

int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);		// error, 编译不会报错, 运行会出错

通过拷贝和移动构造函数初始化

当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被自动调用了。

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

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; //1

    //调用拷贝构造函数
    shared_ptr<int> ptr2(ptr1); //使用ptr1初始化新的智能指针对象
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; //2

    shared_ptr<int> ptr3 = ptr1;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; //3

    //调用移动构造函数
    shared_ptr<int> ptr4(std::move(ptr1));
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; //3

    std::shared_ptr<int> ptr5 = std::move(ptr2);
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;//3

    return 0;
}

        如果使用拷贝的方式初始化共享智能指针对象,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加;如果使用移动的方式初始智能指针对象,只是转让了内存的所有权管理内存的对象并不会增加,因此内存的引用计数不会变化

通过 std::make_shared 初始化

std::make_shared() 是模板函数。

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

T:模板参数的数据类型
Args&&... args :要初始化的数据,如果是通过 make_shared 创建对象,需按照构造函数的参数列表指定
#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Test
{
public:
    Test() 
    {
        cout << "construct Test..." << endl;
    }
    Test(int x) 
    {
        cout << "construct Test, x = " << x << endl;
    }
    Test(string str) 
    {
        cout << "construct Test, str = " << str << endl;
    }
    ~Test()
    {
        cout << "destruct Test ..." << endl;
    }
};

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;

    shared_ptr<Test> ptr2 = make_shared<Test>();
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;

    shared_ptr<Test> ptr3 = make_shared<Test>(520);
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;

    shared_ptr<Test> ptr4 = make_shared<Test>("我是要成为海贼王的男人!!!");
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    return 0;
}

 通过 reset 方法初始化

共享智能指针类提供的 std::shared_ptr::reset 方法函数原型如下:

void reset() noexcept;

template< class Y >
void reset( Y* ptr );

template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );

ptr:指向要取得所有权的对象的指针
alloc:内部存储所用的分配器
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;


void test1()
{

    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    shared_ptr<int> ptr2 = ptr1;
    shared_ptr<int> ptr3 = ptr1;
    shared_ptr<int> ptr4 = ptr1;
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    cout << endl << "after ptr4.reset();" << endl;
    ptr4.reset();
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    shared_ptr<int> ptr5;
    ptr5.reset(new int(250));
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;

    getchar();

}


int main(void)
{
	test1();
	system("pause");
	return 0;
}

对于一个未初始化的共享智能指针,可以通过 reset 方法来初始化,当智能指针中有值的时候,调用 reset 会使引用计数减 1。

获取原始指针

        对基础数据类型来说,通过操作智能指针操作智能指针管理的内存效果是一样的,可以直接完成数据的读写。但是如果共享智能指针管理的是一个对象,那么就需要取出原始内存的地址再操作,可以调用共享智能指针类提供的 get () 方法得到原始地址,其函数原型如下:

#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
    int len = 128;
    shared_ptr<char> ptr(new char[len]);
    // 得到指针的原始地址
    char* add = ptr.get();
    memset(add, 0, len);
    strcpy(add, "我是要成为海贼王的男人!!!");
    cout << "string: " << add << endl;
    
    shared_ptr<int> p(new int);
    *p = 100;
    cout << *p.get() << "  " << *p << endl;
    
    return 0;
}

指定删除器

        当智能指针管理的内存对应的引用计数变为 0 的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。

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

// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p)
{
    delete p;
    cout << "int 型内存被释放了...";
}

int main()
{
    shared_ptr<int> ptr(new int(250), deleteIntPtr);
    shared_ptr<int> ptr2(new int(250), [](int* p) {delete p; }); //删除器函数也可以是 lambda 表达式
    return 0;
}

(二)独占的智能指针(std::unique_ptr)

1、初始化

        std::unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个 unique_ptr 赋值给另一个 unique_ptr。

        std::unique_ptr 不允许复制,但是可以通过函数返回给其他的 std::unique_ptr,还可以通过 std::move 来转译给其他的 std::unique_ptr,这样原始指针的所有权就被转移了,这个原始指针还是被独占的。

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

unique_ptr<int> func()
{
    return unique_ptr<int>(new int(520));
}

int main()
{
    // 通过构造函数初始化
    unique_ptr<int> ptr1(new int(10));
    // 通过转移所有权的方式初始化
    unique_ptr<int> ptr2 = move(ptr1);
    unique_ptr<int> ptr3 = func();

    ptr1.reset();    //使用 reset 方法可以让 unique_ptr 解除对原始内存的管理
    ptr2.reset(new int(250)); //重新指定智能指针管理的原始内存

    //如果想要获取独占智能指针管理的原始地址,可以调用 get () 方法
     cout << *ptr2.get() << endl;	// 得到内存地址中存储的实际数值 250
    


    return 0;
}

2、删除器

        unique_ptr 指定删除器和 shared_ptr 指定删除器是有区别的,unique_ptr 指定删除器的时候需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器。

shared_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// ok
unique_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// error

int main()
{
    using func_ptr = void(*)(int*); 
    // func_ptr 的类型和 lambda表达式的类型是一致的
    unique_ptr<int, func_ptr> ptr1(new int(10), [](int*p) {delete p; });

    return 0;
}

        在 lambda 表达式没有捕获任何变量的情况下是正确的,如果捕获了变量,编译时则会报错。在 lambda 表达式没有捕获任何外部变量时,可以直接转换为函数指针一旦捕获了就无法转换了,如果想要让编译器成功通过编译,那么需要使用可调用对象包装器来处理声明的函数指针:

int main()
{
    using func_ptr = void(*)(int*);
    unique_ptr<int, function<void(int*)>> ptr1(new int(10), [&](int*p) {delete p; });
    return 0;
}

(三)弱引用智能指针(std::weak_ptr)

        弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 * 和 ->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在。

初始化

// 默认构造函数
constexpr weak_ptr() noexcept;

// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;

template <class U> 
weak_ptr (const weak_ptr<U>& x) noexcept;

// 通过shared_ptr对象构造
template <class U> 
weak_ptr (const shared_ptr<U>& x) noexcept;

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

int main() 
{
    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1; //造了一个空 weak_ptr 对象
    weak_ptr<int> wp2(wp1); //通过一个空 weak_ptr 对象构造了另一个空 weak_ptr 对象
    weak_ptr<int> wp3(sp); //通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象
    weak_ptr<int> wp4;
    wp4 = sp; /通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象(这是一个隐式类型转换)
    weak_ptr<int> wp5;
    wp5 = wp3; //通过一个 weak_ptr 对象构造了一个可用的 weak_ptr 实例对象
    
    return 0;
}

常用方法

 use_count()

通过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数。

 expired()

判断观测的资源是否已经被释放。

// 返回true表示资源已经被释放, 返回false表示资源没有被释放
bool expired() const noexcept;

lock()

通过调用 std::weak_ptr 类提供的 lock() 方法来获取管理所监测资源的 shared_ptr 对象,函数原型如下:

shared_ptr<element_type> lock() const noexcept;
#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1, sp2;
    weak_ptr<int> wp;

    sp1 = std::make_shared<int>(520);
    wp = sp1;
    
    //通过调用 lock() 方法,得到一个用于管理 weak_ptr 对象所监测的资源的 共享智能指针对象,
    //使用这个对象初始化 sp2,此时所监测资源的引用计数为 2
    sp2 = wp.lock();
    cout << "use_count: " << wp.use_count() << endl; //2

    sp1.reset(); //共享智能指针 sp1 被重置,weak_ptr 对象所监测的资源的引用计数减 1
    cout << "use_count: " << wp.use_count() << endl; //1
    
    //sp1 重新被初始化,并且管理的还是 weak_ptr 对象所监测的资源,因此引用计数加 1
    sp1 = wp.lock();
    cout << "use_count: " << wp.use_count() << endl; //2

    //共享智能指针对象 sp1 和 sp2 管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是 520
    cout << "*sp1: " << *sp1 << endl; //520
    cout << "*sp2: " << *sp2 << endl; //520

    return 0;
}

reset()

清空对象,使其不监测任何资源。

返回管理 this 的 shared_ptr

        如果在一个类中编写了一个函数,通过这个函数,得到管理当前对象共享智能指针,我们可能会写出如下代码:

        

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

struct Test
{
    shared_ptr<Test> getSharedPtr()
    {
        return shared_ptr<Test>(this);
    }
    
    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }

};

int main() 
{
    shared_ptr<Test> sp1(new Test);
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

use_count: 1
use_count: 1
class Test is disstruct ...
class Test is disstruct ...

        通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针 this 构造了两个智能指针对象 sp1 和 sp2,这二者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象,在离开作用域之后 this 将被构造的两个智能指针各自析构,导致重复析构的错误。

        这个问题可以通过 weak_ptr 来解决,通过 wek_ptr 返回管理 this 资源的共享智能指针对象 shared_ptr。C++11 中为我们提供了一个模板类叫做 std::enable_shared_from_this<T>,这个类中有一个方法叫做 shared_from_this(),通过这个方法可以返回一个共享智能指针,在函数的内部就是使用 weak_ptr 来监测 this 对象,并通过调用 weak_ptr 的 lock() 方法返回一个 shared_ptr 对象。

     

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

struct Test : public enable_shared_from_this<Test>
{
    shared_ptr<Test> getSharedPtr()
    {
        return shared_from_this();
    }
    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }
};

int main() 
{
    shared_ptr<Test> sp1(new Test);
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

use_count: 1
use_count: 2
class Test is disstruct ...

        最后需要强调一个细节:在调用 enable_shared_from_this 类的 shared_from_this () 方法之前,必须要先初始化函数内部 weak_ptr 对象,否则该函数无法返回一个有效的 shared_ptr 对象(具体处理方法可以参考上面的示例代码)。

解决循环引用问题

        智能指针如果循环引用会导致内存泄露:

        

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

struct TA;
struct TB;

struct TA
{
    shared_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 2

        在测试程序中,共享智能指针 ap、bp 对 TA、TB 实例对象的引用计数变为 2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 TA、TB 的实例对象不能被析构,最终造成内存泄露。通过使用 weak_ptr 可以解决这个问题,只要将类 TA 或者 TB 的任意一个成员改为 weak_ptr:

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

struct TA;
struct TB;

struct TA
{
    weak_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 1
class TB is disstruct ...
class TA is disstruct ...

        上面程序中,在对类 TA 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域之后 bp 的引用计数减为 0,类 TB 的实例对象被析构。

        在类 TB 的实例对象被析构的时候,内部的 aptr 也被析构,其对 TA 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 TA 对象的管理也解除了,内存的引用计数减为 0,类 TA 的实例对象被析构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心之所向便是光v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值