C++ | boost库之智能指针

  • Post author:
  • Post category:其他



目录


一、RAII机制


二、智能指针


1.C++98下auto_ptr


2.boost::scoped_ptr


3.boost::shared_ptr


★make_shared()


★shared_ptr应用于标准容器


★定制删除器


★ weak_ptr


4.boost::scoped_ptr、std::unique_ptr与shared_ptr比较


5.boost::intrusive_ptr


★使用方式一


★使用方式二


一、RAII机制

RAII机制在之前的文章中有讲解,具体可以查看

C++ 内存管理

这篇文章。

二、智能指针

向 C ++ 引入异常机制后,智能指针由一种技巧升级为一种非常重要的技术,因为如果没有智能指针,程序员必须保证 new 对象能在正确的时机 delete ,必须到处编写异常捕获代码以释放资源,而智能指针则可以在退出作用域时(无论是因正常流程离开还是因异常离开)总调用 delete 来析构在堆上动态分配的对象。

1.C++98下auto_ptr

auto_ptr 存在一些缺陷,所以新的 C ++ 标准提供了更完善的

unique_ptr、shared_ptr 和 weak_ptr

,而它们正是基于我们接下来要介绍的 boost.smart_ptr 库的。

boost.smart_ptr 库提供了很多种智能指针,常用的有 scoped_ptr、shared_ptr、weak_ptr 和intrusive_ptr。它们是轻量级的对象,其速度与原始指针相差无几,都是异常安全的( exceptionsafe ) ,而且对于所指向的类型 T 也仅有一个很小且很合理的要求:类型 T 的析构函数不能抛出异常。

2.boost::scoped_ptr

scoped_ptr并非是官方的标准,它是C++大牛们自己实现的版本。到了C++11之后官方版本来了,其被命名为unique_ptr。实际上unique_ptr与scoped_ptr功能几乎是一模一样,不过它们之间也有一些细微差别。

差别是什么呢?就是unique_ptr可以对右值进行转移,就是提供了一种特殊方法可以将unique_ptr赋值给另一个unique_ptr,被转移后的unique_ptr也就不能再处理之前管理的指针了。(只要给我们之前的ScopedPtr加上一个移动构造函数和移动赋值运算符就实现unique_ptrr的转移功能了。)

——

细说智能指针 | 音视跳动科技 (avdancedu.com)

scoped_ptr 是一个与 auto_ptr / unique_ptr 很类似的智能指针,它包装了 new 操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但 scoped_ptr 的所有权更加严格,正如其名:这个智能指针只能在本作用域里使用,不希望被转让(

scoped_ptr同时把拷贝构造函数和赋值操作符都声明为私有的 , 禁止对智能指针进行拷贝操作, 保证了被它管理的指针的所有权不能被转让

),一旦scoped_ptr 获取了对象的管理权,我们就无法再从它那里收回来。

如果一个类持有scoped _ ptr 成员变量,那么它也会是不可拷贝和赋值的。

scoped_ptr 是一个行为类似指针的对象实例,而不是指针,所以不允许对一个对象应用 delete 操作, scoped_ptr 会自动帮我们释放资源。如果我们对 scoped_ptr 执行delete操作,会得到一个编译错误。

scoped_ptr 支持有限的比较操作,不能在两个scoped_ptr 之间进行相等或不等测试,默认它仅支持与 nullptr 进行比较(也可以是 NULL 或 O ,因为这两者可以隐式转换为 nullptr ) 。

#include "boost/smart_ptr.hpp"

class Test {
    char* testArray;
public:
    Test()
    {
        std::cout << "构造函数申请内存" << std::endl;
        testArray = new char[1000000]{ '1','2','3'};
    }
    void Print()
    {
        std::cout << testArray[2] << std::endl;
    }
    ~Test()
    {
        std::cout << "析构函数释放内存" << std::endl;
        delete[] testArray;
        testArray = nullptr;
    }
};

void TestScoped_ptr()
{
    boost::scoped_ptr<Test> _scoped_ptr(new Test());
    //测试scoped_ptr 是否持有一个有效的指针
    if (_scoped_ptr) {
        _scoped_ptr->Print();
    }
}

int main()
{
    TestScoped_ptr();
    getchar();
}

3.boost::shared_ptr

boost::shared_ptr是一个最像指针的“智能指针”,它是boost.smart_ptr库中最有价值、最重要的组成部分,boost库的许多组件(甚至还包括一些其他领域的智能指针)都使用了shared_ptr ,所以它被毫无悬念地收入 C ++11 标准。

boost::shared_ptr与std::shared_ptr使用方法相差不大,std::shared_ptr相关知识可查看

C++ 内存管理

,补充如下:

shared_ptr实现的是引用计数型的智能指针(shared_ptr早期名字叫counted_ptr)。 shared_ptr 的名字表明了它与 scoped_ptr 的主要不同之处:它是可以被安全共享的。 shared_ptr 是一个“全功能”的类,有着正常的拷贝、赋值语义,也可以进行 shared_ptr 间的比较,是“最智能”的智能指针。

shared_ptr的aliasing ( ) 函数:别名构造函数,是不增加引用计数的特殊用法。

shared_ptr的 reset ( ) 函数的行为与 scoped_ptr 不尽相同,它的作用是将引用计数减 1,停止对指针的共享,除非引用计数为 0,否则不会发生删除操作。带参数的 reset ( ) 函数类似相同形式的构造函数,在原指针引用计数减 1 的同时改为管理另一个指针。

★make_shared()

因为 shared_ptr 的构造还需要 new 调用,这会导致代码的某种不对称性。虽然shared_ptr 很好地包装了 new 表达式,但过多的显式 new 操作符也是个问题,显式 new 调用应该使用工厂模式来解决。因此,smart_ptr 库提供了一个工厂函数make_shared ( ) 来消除显式的 new 调用。

make_shared ( ) 函数可以接收若干个参数,然后把它们传递给类型 T 的构造函数,创建一个shared ptr < T > 的对象并返回。通常使用make_shared ( ) 函数要比直接创建 shared_ptr 对象的方式快且高效,因为它内部仅分配一次内存,消除了 shared_ptr 构造时的“开销”。

void TestShared_ptr()
{
    boost::shared_ptr<Test> _shared_ptr1(new Test());
    boost::shared_ptr<Test> _shared_ptr2 = boost::make_shared<Test>();
    if (_shared_ptr2 == _shared_ptr1)
    {
        std::cout << "两个指针相同" << std::endl;
    }
}

除了make_shared( ),smart_ptr库还提供了一个allocate_shared( ),它比make_shared( )多接收一个定制的内存分配器类型的参数,其他方面二者相同。



三个make函数

make函数用来把一个任意参数的集合完美转移给一个构造函数从而生成动态分配内存的对象,并返回一个指向那个对象的灵巧指针。

  1. make_unique;
  2. make_shared;
  3. allocate_shared:它像make_shared一样,除了第一个参数是一个分配器对象,用来进行动态内存分配。

★shared_ptr应用于标准容器

有两种方式可以将 shared_ptr 应用于标准容器:一种方式是将容器作为 shared _ ptr 的管理对象;另一种方式是将 shared _ ptr 作为容器的元素。我们主要探讨的是第二种。

    boost::shared_ptr<std::vector<Test>> shared_vector;
    std::vector<boost::shared_ptr<Test>> vector_sharedItem(10);

void TestVectorSharedPtr()
{
    std::vector<boost::shared_ptr<Test>> vector_sharedItem(10);

    //方式一
    //for (size_t i = 0; i < 10; i++)
    //{
    //    vector_sharedItem[i] = boost::make_shared<Test>();
    //    vector_sharedItem[i]->Print();
    //}

    //方式二
    for (auto pos = vector_sharedItem.begin(); pos != vector_sharedItem.end(); ++pos)
    {
        *pos = boost::make_shared<Test>();
        (*pos)->Print();
        (**pos).Print();
    }
}

在上述代码中,需要注意迭代器和 operator[ ] 的用法,因为容器内存储的是 shared_ptr,我们必须对迭代器 pos 使用一次解引用操作符“*”以获得shared_ptr,再对 shared_ptr使用解引用操作符“*”才能操作真正的值。 *(*pos)也可以直接写成 ** pos ,但前者更清晰,而后者很容易让人迷惑。

★定制删除器

shared_ptr 另一种形式的构造函数 shared_ptr(Y *p,D d)涉及shared_ptr 的另一个重要概念 —— 删除器。它的第一个参数是要被管理的指针,含义与其他构造函数的参数相同。而第二个参数则告诉 shared_ptr 在析构时不要使用delete来操作指针 p,而要用 d 来操作,即把 delete p换成 d(p)。

删除器 d 可以是一个函数对象,也可以是一个函数指针,只要它能够像函数那样被调用 , 使得d ( p ) 成立即可。对删除器的要求是它必须可拷贝,其行为也必须像 delete 那样,不能抛出异常。为了配合删除器的工作,shared_ptr 提供一个自由函数 get_deleter ( ),它能够返回内部的删除器指针。

有了删除器的概念,我们就可以用 shared_ptr 实现管理任意资源。只要这种资源提供了它自己的释放操作,shared _ ptr 就能够保证它自动释放。

void MyDelete(Test* t)
{
    delete[] t;
}
void TestSharedPtrArray()
{
    std::shared_ptr<Test> shared_ptr(new Test[100], MyDelete);//参数二只需要把函数名传递给shared_ptr即可,或者在函数名前加上取地址操作符“ & ”
}

★ weak_ptr

查看

C++内存管理

这篇文章。

4.boost::scoped_ptr、std::unique_ptr与shared_ptr比较

关于std::unique_ptr,具体可查看

C++ 内存管理

文章,补充如下:

C++11标准下的unique_ptr不支持通过make_unique函数来创建(C++14标准补上了这个漏洞),在C++14标准之前,boost.smart_ptr 库特意在头文件<boost/smart_ptr/make_unique.hpp>里实现了 make_unique ( ) 函数。

boost::make_unique()的用法和C++14标准下std::make_unique()的用法是一样的。

#include "boost/smart_ptr/make_unique.hpp"
void TestMake_unique()
{
    std::unique_ptr<Test> unique_ptr1 = boost::make_unique<Test>(Test());
    
    std::unique_ptr<Test> unique_ptr2 =  std::make_unique<Test>(Test());//C++14支持
}

std::unique_ptr

boost::scoped_ptr

shared_ptr
unique_ptr 不仅能够代理 new创建的单个对象 ,

也能够代理 new [ ] 创建的数组对象。
scoped_ptr仅能够代理 new创建的单个对象。 仅能够代理 new创建的单个对象(要代理数组对象,除非使用智能指针定义一个删除器)
可以在作用域内管理指针,不允许拷贝构造和拷贝赋值(但支持转移语义)
可以在作用域内管理指针,不允许拷贝构造和拷贝赋值 可共享,允许拷贝构造、拷贝赋值
可以像原始指针一样进行比较 只能支持与 nullptr 进行比较 支持比较运算=、<
支持工厂函数make_unique()来消除显示的new调用 不需要工厂函数make_scoped(),因为boost::scoped_ptr不能拷贝也不能转移。 支持工厂函数make_shared()来消除显示的new调用

5.boost::intrusive_ptr

intrusive_ptr 类似shared_ptr,也是一种引用计数型智能指针,但需要额外增加一些代码才能使用它。

如果现存代码已经有了引用计数机制管理的对象,那么 intrusive_ptr 是一个非常好的选择,它可以包装已有对象从而得到与 shared_ptr 类似的智能指针。它自己不直接管理引用计数 , 而是调用intrusive_ptr_add_ref(T *p)、intrusive_ptr_release(T *p)来间接管理引用计数。

intrusive_ptr的构造函数和 reset( ) 相比还多出一个 add_ref 参数,它表示是否增加引用计数,如果add_ref = = false,那么它就相当于 weak_ptr,只是简单地观察对象。

★使用方式一

1.自己实现一个带有引用计数的类;

2.实现两个回调函数intrusive_ptr_add_ref(T *p)、intrusive_ptr_release(T *p)。

在 intrusive_ptr_release( ) 函数中必须检查引用计数,因为 intrusve_ptr 不负责销毁实例,所以这个工作必须由我们自己完成。

#include "boost/smart_ptr/intrusive_ptr.hpp"
class counted_data {
private:
    char* testArray;
public:
    int m_count = 0; //引用计数
    counted_data()
    {
        std::cout << "构造函数申请内存" << std::endl;
        testArray = new char[1000000]{ 'a','b','c' };
    }
    void Print()
    {
        std::cout << testArray[2] << std::endl;
    }
    ~counted_data()
    {
        std::cout << "析构函数释放内存" << std::endl;
        delete[] testArray;
        testArray = nullptr;
    }
};

//实现两个回调
void intrusive_ptr_add_ref(counted_data* p)
{
    ++p->m_count;
}
void intrusive_ptr_release(counted_data* p)
{
    if (--p->m_count == 0)
    {
        delete p;
    }
}
void TestIntrusive_ptr()
{
    boost::intrusive_ptr<counted_data> ptr(new counted_data());
    std::cout << ptr->m_count << std::endl;//1
    ptr->Print();

    boost::intrusive_ptr<counted_data> ptr1(ptr);
    std::cout << ptr1->m_count << std::endl;//2
    ptr1->Print();

    boost::intrusive_ptr<counted_data> ptr2(ptr.get(), false);
    std::cout << ptr2->m_count << std::endl;//2

    Sleep(1000);

    ptr2.reset();//释放ptr2中内置的指针指向的空间
    std::cout << ptr1->m_count << std::endl;//1

}

★使用方式二

写一个类,继承boost::intrusive_ref_counter。

intrusive_ref_counter内部定义了一个计数器变量m_ref_counter ,使用模板参数配置策略类实现了引用计数的增减,默认的策略是线程安生的thread_safe_counter 。

intrusive_ref_counter需要被继承使用,这样其子类就会自动获得引用计数的能力。

#include "boost/smart_ptr/intrusive_ptr.hpp"
#include "boost/smart_ptr/intrusive_ref_counter.hpp"
class CountedData:public boost::intrusive_ref_counter< CountedData>{
private:
    char* testArray;
public:
    CountedData()
    {
        std::cout << "构造函数申请内存" << std::endl;
        testArray = new char[1000000]{ 'a','b','c' };
    }
    void Print()
    {
        std::cout << testArray[2] << std::endl;
    }
    ~CountedData()
    {
        std::cout << "析构函数释放内存" << std::endl;
        delete[] testArray;
        testArray = nullptr;
    }
};
void TestIntrusiveRefCounter()
{
    boost::intrusive_ptr<CountedData> ptr(new CountedData());
    std::cout << ptr->use_count() << std::endl;//1
    ptr->Print();

    boost::intrusive_ptr<CountedData> ptr1(ptr);
    std::cout << ptr1->use_count() << std::endl;//2
    ptr1->Print();

    boost::intrusive_ptr<CountedData> ptr2(ptr.get(), false);
    std::cout << ptr2->use_count() << std::endl;//2

    Sleep(1000);

    ptr2.reset();//释放ptr2中内置的指针指向的空间
    std::cout << ptr1->use_count() << std::endl;//1
}



版权声明:本文为weixin_39766005原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。