C++ 三种智能指针及其设计实现unique_ptr、 share_ptr 指针

0、差不多春节啦。。。。。 好久没有写博客,写一写吧。。。。。。 祝大家嗨皮,提前恭喜发财

1、三种智能指针的使用方法

C++ 有3种指针:share_ptr, unique_ptr, weak_ptr

1.1)unique_ptr 指针 

std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。【拷贝构造函数、赋值重载运算符是private or deleted的

std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法

 创建unique_ptr 指针的3种方式:

 
// 1.使用reset方法
    unique_ptr<SmartPtrClass> smart1;
    smart1.reset(new SmartPtrClass("smart1"));
 
// 2. 使用make_unique 方法,C++14支持。括号里的是参数
    unique_ptr<SmartPtrClass> smart3 = make_unique<SmartPtrClass>("smart3");
 
// 3. () 方法
    unique_ptr<SmartPtrClass> smart2(new SmartPtrClass("smart2"));

unique_ptr 不能赋值或者调用复制构造函数,但是可以调用移动构造函数,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个 unique_ptr,并且原始 unique_ptr 不再拥有此资源。【下列ptrA 指针将变为空指针

在这里插入图片描述

class SmartPtrClass{
private:
    string className;
public:
    SmartPtrClass(string m_name):className(m_name){
        cout << className << " constructor" << endl;
    }
    ~SmartPtrClass(){
        cout << className << " destructor" << endl;
    }
 
    void show(){
        cout << "this is object: " << className << " show" << endl; 
    }
};
 
 
int main(){
 
    unique_ptr<SmartPtrClass> smart2(new SmartPtrClass("smart2"));
    (*smart2).show();
 
    // 方法3创建对象,括号的是参数
    unique_ptr<SmartPtrClass> smart3 = make_unique<SmartPtrClass>("smart3");
    smart3->show();
 
    // smart2释放所有权给 tmp,但是 smart2不会调用析构函数,只能通过 delete tmp1
    SmartPtrClass* tmp1 = smart2.release();
 
    // 可以通过move 函数将 smart2 转移给 tmp,此时可以释放析构函数
    unique_ptr<SmartPtrClass> tmp2(std::move(smart3));
    // 无法再调用 smart2,会运行错误;smart2 已经是空指针了,会打印下列的log
    if (!smart2){
        cout << "smart2 pointer is null =======" << endl;
    }
    if (!smart3){
        cout << "smart3 pointer is null ========" << endl;
    }
 
    cout << "===========testSmartPtr===========" << endl;
 
}
 
// 输出结果:
smart2 constructor
this is object: smart2 show
smart3 constructor
this is object: smart3 show
smart2 pointer is null =======
smart3 pointer is null ========
===========testSmartPtr===========
smart3 destructor

由上可知:

通过 release 方法释放所有权、通过移动语义std::move转移所有权的区别:

1. release 方法不能调用析构函数【需要通过delete】;move可以调用析构函数

相同点:

源指针变为空指针

auto_ptr 指针被废弃的原因

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
// 主要是因为下列赋值语句可以成功,拷贝构造函数不是private 的
vocaticn = ps;

上述赋值语句将完成什么工作呢?如果 ps 和 vocation 是常规指针,则两个指针将指向同一个 string 对象。这是不能接受的,因为程序将试图删除同一个对象两次,一次是 ps 过期时,另一次是 vocation 过期时。

要避免这种问题,方法有多种:

  1. 定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
  2. 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更严格。【转让了所有权,则源指针是为空指针的】
  3. 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加 1,而指针过期时,计数将减 1,。当减为 0 时才调用 delete。这是 shared_ptr 采用的策略。

unique_ptr 比 auto_ptr 更加安全,因为 auto_ptr 有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr 则禁止了拷贝语义,但提供了移动语义,即可以使用 std::move() 进行控制权限的转移,如下代码所示: 

unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt);	//编译出错,已禁止拷贝
unique_ptr<string> upt1=upt;	//编译出错,已禁止拷贝
unique_ptr<string> upt1=std::move(upt);  //控制权限转移, upt1 为空指针
 
auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt);	//编译通过
auto_ptr<string> apt1=apt;	//编译通过

1.2)shared_ptr 指针 

shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在 memory 文件中,命名空间为 std。shared_ptr最初实现于Boost库中,后由 C++11 引入到 C++ STL。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:

(1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
(2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;
(3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。

3种创建 shared_ptr 指针的方法:

// 1. () 括号形式
    shared_ptr<SmartPtrClass> shSmart1(new SmartPtrClass("shSmart1"));
 
// 2. make_shared形式
    shared_ptr<SmartPtrClass> shSmart2 = make_shared<SmartPtrClass>("shSmart2");
 
// 3. reset 方法
    shared_ptr<SmartPtrClass> shSmart3;
    shSmart3.reset(new SmartPtrClass("shSmart3"));
    shSmart3->show();

通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数, 并通过use_count()来查看一个对象的引用计数

// 可以直接使用赋值运算
    shared_ptr<SmartPtrClass> shTmp1 = shSmart1;
    auto shTmp2 = shSmart1;
    cout << "shSmart1 ref cout: " << shSmart1.use_count() << endl;
    shTmp1.reset();
    cout << "shSmart1 ref cout afther reset: " << shSmart1.use_count() << endl;
    auto orig = shTmp2.get();
 
    cout << "===========testSmartPtr===========" << endl;
 
//输出log:
shSmart1 ref cout: 3
shSmart1 ref cout afther reset: 2
===========testSmartPtr===========

1.3)weak_ptr  指针

std::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。弱引用不会引起引用计数增加。weak_ptr 为解决循环引用的问题,如下代码:

struct A;
struct B;
 
struct A {
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
struct B {
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;
}

运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 a,b,这使得 a,b 的引用计数均变为了 2,而离开作用域时,a,b 智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 a,b 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,

解决这个问题的办法就是使用弱引用指针 std::weak_ptrstd::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如图 5.2 所示:

struct A;
struct B;
 
struct A {
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
struct B {
// 使用 weak 指针
    std::weak_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;

// weak_ptr管理的资源不能被直接访问,需要通过lock函数从其中获取shared_ptr才可以被访问。             
    std::shared_ptr<A> tempA = b->pointer.lock();
}

1.4)智能指针使用的场景

(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:

  1. 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
  2. 两个对象包含都指向第三个对象的指针;
  3. STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。

如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。
 

2. 实现 unique_ptr 智能指针

智能指针的实现原理:

利用在栈上创建的类,其会自动调用析构函数。创建一个包装类,包装对应的指针,重载运算符:* 与 -> 让其对象像是在指针上操作。该类在栈上创建,当对象不再使用时,自动析构,在析构函数中delete 对应包装的指针

实现需要注意的点:

1. 构造函数的隐式变换

2. 编写模板类

3. 禁止拷贝构造函数和 赋值构造函数

4.  移动构造、移动赋值重载运算符 

5. 判断对象是否存在 ,重载 operator bool (),explicit关键字不能少

6. 如果未标记noexcept,标准库将产生一个额外的操作,增加了开销。

7. 使用 std::swap 提高运行速度

// 实现一个 unique_ptr 智能指针, T 是传入的对象
template <typename T>
class UniquePtr{
 
private:
    T* ptr = nullptr;
 
public:
    // 拷贝构造函数和 赋值构造函数是不能使用的
    UniquePtr(const UniquePtr& copy) noexcept = delete;
    // 赋值构造函数 不能使用
    UniquePtr& operator = (const UniquePtr& copy) noexcept = delete;
 
    // 交换 2 个指针
    void swap(UniquePtr<T>& src){
        std::swap(ptr, src.ptr);
    }
 
    // 移动所有权是可以使用的,移动构造函数和移动的赋值重载运算符是可以使用的
    UniquePtr(UniquePtr&& uptr) noexcept {
        cout << "UniquePtr move construtor" << endl;
        // 或者直接使用下列swap函数
        // uptr.swap(*this);
        ptr = uptr.ptr;
        delete uptr.ptr;
        uptr.ptr = nullptr;
    }    
 
    // 移动构造函数和移动的赋值重载运算符
    UniquePtr& operator = (const UniquePtr&& uptr) noexcept{
        cout << "UniquePtr move UniquePtr& operator = " << endl;
        // 或者直接使用下列swap函数
        // uptr.swap(*this);
        if (*this != uptr){
            if (ptr)
                delete ptr;
            ptr = uptr.ptr;
            delete uptr.ptr;
            uptr.ptr = nullptr;
        }
        return *this;
    }
 
    // 析构函数删除传入的指针
    ~UniquePtr(){
        cout << "UniquePtr destructor" << endl;
        delete ptr;
    }
 
    // 构造函数, 需要使用隐式
    explicit UniquePtr(T* _ptr){
        cout << "UniquePtr costructor" << endl;
        ptr = std::move(_ptr);
    }
 
    UniquePtr():ptr(nullptr){
        cout << "UniquePtr costructor. params nullptr" << endl;
    }
 
    // 用于判断对象是否存在,explicit关键字不能少
    explicit operator bool () noexcept{
        cout << "UniquePtr operator bool" << endl;
        return this->ptr;
    }
 
    // ========== release, get, reset 方法 ==========
    T* release() noexcept{
        cout << "UniquePtr release" << endl;
        // 需要先缓存前面的 指针
        T* tmp = ptr;
        delete ptr;
        ptr = nullptr;
        return tmp;
    }
 
    T* get() noexcept{
        return ptr;
    }
 
    void reset (T* srcPtr) noexcept{
        delete ptr;
        ptr = nullptr;
        std::swap(ptr, srcPtr);
    }
 
    // ========== 重载 * 和 -> ==========
    T* operator -> () noexcept{
        return this->ptr;
    }
 
    T& operator * () noexcept{
        return *this->ptr;
    }
 
};
 
class TestClass{
 
private:
    int value = 3;
 
public:
    TestClass(){
        cout << "TestClass costructor" << endl;
    }
 
    ~TestClass(){
        cout << "TestClass destructor" << endl;
    }
 
    void show(){
        cout << "TestClass show======" << endl;
    }
 
    void showValue(){
        cout << "TestClass showValue======" << value << endl;
    }
 
};
 
// 实现一个 shared_ptr 智能指针
 
 
void testSmartPointer(){
    cout << "=========testSmartPointer=========" << endl;
    UniquePtr<TestClass> tc(new TestClass);
    // UniquePtr<TestClass> tc2(tc);
    // UniquePtr<TestClass> tc2 = tc;
    tc->show();
    (*tc).show();
    UniquePtr<TestClass> tc1(std::move(tc));
    tc1->show();
    // tc 还是有所有权
    tc->show();
    // 如果需要判断,则必须重载 operator bool
    if (tc){
        cout << "tc is not NULL" << endl;
    }
    // value 资源不可用,程序崩溃
    // tc->showValue();
 
    UniquePtr<TestClass> tc3;
    tc3.reset(new TestClass);
    tc3->show();
 
    cout << "=========testSmartPointer=========" << endl;
}

3. 实现 share_ptr 智能指针

首先先明确的一点:为什么赋值运算符重载需要先减去 1

1. 对于对SharedPtr 的重新赋值,需要调用指针的析构函数

2.  指针的拷贝形式是:浅拷贝形式,两个count 值是相同的

    // 重载赋值 = 运算符,需要ref count 减去1
    SharedPtr<PointerType>& operator = (const SharedPtr<PointerType>& src) noexcept{
        cout << "SharedPtr  operator = " << endl;
        if (this == &src){
            return *this;
        }
        if (counter->reduceRef() == 0){
            delete ptr;
            delete counter;
            cout << "重新赋值,删除原始的指针========= " << endl;
        }
        ptr = src.ptr;
        // 浅拷贝形式,两个count 值是相同的
        counter = src.counter;
        // 增加一个计数,
        counter->addRef();
        return *this;
    }
 
 
    // why: 为什么重载 operator =, 使用if (counter->reduceRef() == 0)
    SharedPtr<TestClass> s5(new TestClass);
    cout << "before s5 ref cout: " << s5.use_count() << endl;
    SharedPtr<TestClass> s6(new TestClass);
    s5 = s6;
    // 浅拷贝形式,两个count 值是相同的
    cout << "afther s5 ref cout: " << s5.use_count() << endl;
    cout << "afther s6 ref cout: " << s6.use_count() << endl;
 
// ================= shared_ptr 类库 =================
    shared_ptr<TestClass> ptr1(new TestClass);
    shared_ptr<TestClass> ptr2(new TestClass);
    cout << "before shared_ptr ref cout: " << ptr1.use_count() << endl;
    // 会先调用下 TestClass 的析构函数
    ptr1 = ptr2;
    cout << "afther shared_ptr ref cout: " << ptr1.use_count() << endl;
    cout << "afther shared_ptr ref cout: " << ptr2.use_count() << endl;
 
// =======输出:
TestClass costructor
SharedPtr costructor
before s5 ref cout: 1
TestClass costructor
SharedPtr costructor
SharedPtr  operator = 
TestClass destructor
重新赋值,删除原始的指针========= 
afther s5 ref cout: 2
afther s6 ref cout: 2
 
------
TestClass costructor
TestClass costructor
before shared_ptr ref cout: 1
TestClass destructor
afther shared_ptr ref cout: 2
afther shared_ptr ref cout: 2
 
 

共享 shared 智能指针需要注意的几个点:

1. = 赋值重载运算符需要先减去 1 个计算

2. 在拷贝构造函数 和 赋值运算符去 增加 1 个计数引用

3. 在析构函数 和 赋值重载运算符去 减去 1 个计数引用

4. 如 unique 指针,在构造函数 增加隐式声明,noexcept。

class RefCounter{
 
private:
    long count;
 
public:
    RefCounter(long _count){
        count = _count;
    }
 
    long reduceRef(){
// c++标准库中的操作是一个原子操作,且要保证线程安全(如加锁操作)
        return --count;
    }
 
    void addRef(){
        ++count;
    }
 
    long getCount(){
        return count;
    }
 
};
 
// 实现一个 shared_ptr 智能指针
template <typename PointerType>
class SharedPtr{
 
private:
    PointerType* ptr;
    RefCounter* counter;
 
public:
    explicit SharedPtr(PointerType* _ptr = nullptr){
        cout << "SharedPtr costructor" << endl;
        ptr = _ptr;
        if (ptr){
            counter = new RefCounter(1);
        }else{
            counter = new RefCounter(0);
        }
    }
 
    ~SharedPtr(){
        if (counter->reduceRef() == 0){
            cout << "SharedPtr destructor" << endl;
            delete ptr;
            delete counter;
        }else{
            cout << "SharedPtr not destructor" << endl;
        }
    }
 
    // 重载赋值 = 运算符,需要ref count 减去1
    SharedPtr<PointerType>& operator = (const SharedPtr<PointerType>& src) noexcept{
        cout << "SharedPtr  operator = " << endl;
        if (this == &src){
            return *this;
        }
        if (counter->reduceRef() == 0){
            delete ptr;
            delete counter;
        }
        ptr = src.ptr;
        counter = src.counter;
        // 增加一个计数
        counter->addRef();
        return *this;
    }
 
    // 拷贝构造函数
    SharedPtr(const SharedPtr<PointerType>& copy){
        cout << "SharedPtr copy constructor " << endl;
        if (this != &copy){
            ptr = copy.ptr;
            counter = copy.counter;
            counter->addRef();
        }
    }
 
    // get函数
    PointerType* get() noexcept{
        return ptr;
    }
 
    // release 函数
    void release(){
        counter->reduceRef();
    }
 
    // use_count 函数
    long use_count() noexcept{
        return counter->getCount();
    }
 
    // 转换符 bool
    explicit operator bool () noexcept {
        return ptr;
    }
 
    // 重载 * 和 -> 预算符
    PointerType* operator -> () noexcept{
        return ptr;
    }
 
    PointerType operator * () noexcept{
        return *ptr;
    }
 
 
};

猜你喜欢

转载自blog.csdn.net/qq_40587575/article/details/128720090