智能指针shared_ptr、unique_ptr、weak_ptr和弃用的auto_ptr

一. 相同不同点概述

相同点:
unique_ptr,shared_ptr和weak_ptr都是C++11中引入的智能指针,用于管理动态分配的内存(auto_ptr是C++98引入的智能指针,并在C++11中被弃用)。

区别:
unique_ptr是独占的智能指针,只能有一个指针指向内存,它的所有权不能被复制或转移。当它超出作用域,或被显式销毁时,所指向的对象会被自动释放。
shared_ptr允许多个智能指针指向同一块内存,它使用引用计数来追踪有多少个指针指向内存。当最后一个指向此内存的shared_ptr离开作用域或显式被销毁时,内存会被释放。
weak_ptr是一个弱引用的智能指针,它可以从一个shared_ptr或另一个weak_ptr对象构造,可以通过调用lock()方法获得一个指向所管理对象的shared_ptr。weak_ptr不会影响所指向对象的引用计数,所以可以用来防止互相引用时的死锁。当weak_ptr指向的shared_ptr被销毁时,weak_ptr会自动失效。weak_ptr一般和shared_ptr配合一起使用,用在那些可能出现互相引用的场景。

二. shared_ptr用法总结

智能指针 std::shared_ptr 在代码中的用法涉及示例如下:

  1. 创建 std::shared_ptr 指向动态分配的内存:
std::shared_ptr<int> ptr = std::make_shared<int>(42);
  1. 共享对象的所有权, 持有多个指向相同对象的 shared_ptr:
auto ptr1 = std::make_shared<int>(10);
auto ptr2 = ptr1;  // 多个 shared_ptr 指向同一块内存
  1. 传递给函数:
void someFunction(std::shared_ptr<int> ptr) {
    // 函数中使用 shared_ptr
}

// 传递 shared_ptr 到函数
someFunction(ptr);
  1. 获取引用计数:
std::shared_ptr<int> ptr = std::make_shared<int>(42);
int count = ptr.use_count();  // 获取引用计数
  1. 重置为nullptr,释放当前指向的对象并将智能指针置为空:
ptr.reset();

或者shared_ptr,使其指向另一个动态分配的int对象,如下:

std::shared_ptr<int> ptr1(new int(10));
ptr1.reset(new int(20)); // 重置shared_ptr,使其指向另一个动态分配的int对象
  1. 使用自定义删除器:
std::shared_ptr<int> ptr(new int, myDeleter);  // 使用自定义删除器
  1. 检查引用是否有效:
std::shared_ptr<int> ptr = std::make_shared<int>(42);
if (ptr) {
    // ptr 指向对象有效
}

总的来说, shared_ptr 提供了通用的内存管理功能,允许多个指针共享对象的所有权,而不需要手动进行内存管理。此外还能方便地查看引用计数,并可以选择是否使用自定义删除器。


而unique_ptr基本和shared_ptr一致,但是unique_ptr没有拷贝功能,也没有引用计数。所以在C++11中没有make_unique()函数,需要用new来进行内存分配,但是在C++14中引入了make_unique。


weak_ptr 的出现是为了防止shared_ptr互相引用,不能销毁的死锁问题。并且weak_ptr提供了一种获取 shared_ptr 的安全方式,以避免因为 shared_ptr 指向的对象已经被释放而导致访问悬空指针的问题。这正是 weak_ptr 类似于一个"观察者"的角色,它可以监视一个 shared_ptr 的生命周期。

三. 三类指针用法示例。

#include <iostream>
#include <memory>

class TestClass {
public:
    TestClass() {
        std::cout << "Constructor called" << std::endl;
    }
    ~TestClass() {
        std::cout << "Destructor called" << std::endl;
    }
};

int main() {
    // C++11中没有make_unique, C++14中才有。在11中只能用new的方式给unique_ptr申请内存
    // 使用 unique_ptr 和 new
    std::unique_ptr<TestClass> uniquePtr(new TestClass());//正确,调用unique_ptr的构造函数
    //std::unique_ptr<TestClass> uniquePtr = new TestClass();//错误,unique_ptr不支持从裸指针进行初始化,
    //即不支持拷贝构造函数和赋值运算符。

    // 使用 shared_ptr 和 make_shared
    std::shared_ptr<TestClass> sharedPtr1 = std::make_shared<TestClass>();
    std::shared_ptr<TestClass> sharedPtr2 = sharedPtr1;  // sharedPtr2和sharedPtr1指向相同的内存
    
    // 使用 weak_ptr
    std::weak_ptr<TestClass> weakPtr = sharedPtr1;

    std::cout << "use_count of sharedPtr1: " << sharedPtr1.use_count() << std::endl;  // 输出引用计数

    // weak_ptr 检查所指向的 shared_ptr 是否存活
    if (auto temp = weakPtr.lock()) {
        std::cout << "Pointer is still alive" << std::endl;
    } else {
        std::cout << "Pointer is expired" << std::endl;
    }

    return 0;
}

输出结果是:

Constructor called
Constructor called
use_count of sharedPtr1: 2
Pointer is still alive
Destructor called
Destructor called

其中,引用计数为2,说明是sharedPtr1的1次+sharedPtr2的1次,而weakPtr不会增加引用计数的次数。

四. shared_ptr引用计数详解

shared_ptr 的引用计数是通过一种称为“控制块”(control block)的结构来实现的。控制块通常会存储引用计数以及指向堆上分配的对象的指针。每当创建一个新的 shared_ptr 时,实际上是在管理这个共享的控制块,而不是直接管理所指向的对象。

以下是一个简化的示例,展示了 shared_ptr 引用计数的概念:

#include <iostream>

template <typename T>
class SharedPtr {
private:
    struct ControlBlock {
        T* data;
        int referenceCount;

        ControlBlock(T* ptr) : data(ptr), referenceCount(1) {}
    };

    ControlBlock* controlBlock;

public:
    SharedPtr(T* ptr) : controlBlock(new ControlBlock(ptr)) {}

    SharedPtr(const SharedPtr& other) : controlBlock(other.controlBlock) {
        controlBlock->referenceCount++;
    }

    ~SharedPtr() {
        if (--controlBlock->referenceCount == 0) {
            delete controlBlock->data;
            delete controlBlock;
        }
    }
};

int main() {
    SharedPtr<int> ptr1(new int(42));
    SharedPtr<int> ptr2 = ptr1;

    return 0;
}

在上述示例中,当创建 SharedPtr 对象时,会同时创建一个 ControlBlock 对象来管理引用计数。在拷贝构造函数中,引用计数会增加,当对象超出作用域时,引用计数减少。当引用计数为0时,ControlBlock 会负责释放内存。

这仅仅是一个简化的示例,真正的 shared_ptr 实现可能会更加复杂,并且还会处理多线程下的安全性等额外的问题。

五. auto_ptr和unique_ptr

auto_ptr是C++98标准引入的智能指针,用于管理动态分配的对象。它包含在头文件中,并位于std命名空间中。auto_ptr可以做到RAII(资源获取就是初始化),但是它具有潜在的安全和所有权转移问题,它不支持对资源的拷贝,所以容器在进行元素拷贝时可能会引起资源的不正确释放,从而导致悬垂指针问题。

使用C++11编译器编译auto_ptr代码可能会导致一些警告或错误。所以在C++11中将其弃用了。
C++11标准引入了更为安全和灵活的unique_ptr。unique_ptr引入了移动语义和禁止拷贝的特性,提供更好的语义和安全性。

移动语义是C++11引入的重要特性,它允许可移动对象的资源在不产生副本的情况下进行转移,从而提高性能并减少不必要的内存开销。
移动语义通过引入右值引用和移动构造函数来实现。右值引用是使用双 && 符号声明的引用类型,可以绑定到临时对象或表达式的结果,而移动构造函数允许将资源从一个对象转移到另一个对象,而不进行深层拷贝。移动语义的引入大大改善了在分配和管理资源时的性能和内存开销。

六. weak_ptr的lock()功能和用法

weak_ptr 用于解决 shared_ptr 循环引用问题。使用shared_ptr时可能会出现循环引用,这将导致资源无法正确释放。而weak_ptr则不会有这个问题。
weak_ptr 指向一个由 shared_ptr 管理的资源,但它并不增加引用计数,也不影响资源的生命周期。
那weak_ptr如何知道当前对象是否已经被释放呢?用lock()方法。
weak_ptr 可以通过调用 lock() 方法获得一个指向所管理对象的 shared_ptr。若被 weak_ptr 管理的对象已经被释放,lock() 会返回一个空的 shared_ptr。如果对象还存在,lock() 则会返回一个有效的 shared_ptr。

以下是一个简单的例子来说明 lock() 的用法:

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> shared = std::make_shared<int>(42);
    std::weak_ptr<int> weak = shared;

    // 使用 lock 获取 shared_ptr
    if (auto locked = weak.lock()) {
        // locked 是一个有效的 shared_ptr
        std::cout << "shared_ptr有效, 内容为" << *locked << std::endl;
    } else {
        // shared_ptr 已经释放
        std::cout << "shared_ptr 已经释放" << std::endl;
    }

    // 离开作用域后,shared_ptr 释放
    shared.reset();

    // 再次使用 lock 获取 shared_ptr
    if (auto locked = weak.lock()) {
        // locked 是一个有效的 shared_ptr
        std::cout << "shared_ptr有效, 内容为" << *locked << std::endl;
    } else {
        // shared_ptr 已经释放
        std::cout << "shared_ptr 已经释放" << std::endl;
    }

    return 0;
}

结果是:

shared_ptr有效, 内容为42
shared_ptr 已经释放

七、智能指针来管理动态分配的数组

《effective C++》里说,shared_ptr等智能指针在析构函数里做的是delete,而不是delete[]。这会导致“首尾不呼应”。所以不应该在动态分配的array上使用shared_ptr等智能指针。如:

std::shared_ptr<int> spi(new int[1024]); //馊主意!会用上错误的delete形式。

但《effective C++》比较老,可能说的不够正确。
其实我们可以自定义智能指针的删除器,自己实现delete[],就保证了“首尾呼应”。如下:

std::shared_ptr<int> spi(new int[1024], [](int* spi){ delete[] spi; });
  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是C++中三种智能指针的实现原理和区别。 首先,我们需要明确一下什么是智能指针智能指针是一种C++中的类,它的行为类似于指针,但是它具有一些额外的功能,比如自动内存管理。智能指针能够自动释放所管理的对象,从而避免内存泄漏和野指针的问题。 下面我们分别介绍shared_ptrunique_ptrweak_ptr三种智能指针的实现原理和区别。 ## shared_ptr shared_ptr是一种引用计数智能指针,它的实现原理是通过使用引用计数来跟踪有多少个shared_ptr对象指向同一个对象。每当一个新的shared_ptr对象指向该对象时,引用计数就会增加1,当一个shared_ptr对象被销毁时,引用计数就会减少1。当引用计数变为0时,就表示没有任何shared_ptr对象指向该对象,此时该对象将被自动销毁。 shared_ptr的优点是可以共享资源,缺点是有可能出现循环引用的问题,导致内存泄漏。为了避免这个问题,C++11中引入了weak_ptr。 ## unique_ptr unique_ptr是一种独占式智能指针,它的实现原理是通过禁止拷贝和赋值来保证同一时间只有一个unique_ptr对象指向一个对象。当一个unique_ptr对象被销毁时,它所管理的对象也将会被销毁。为了更好地支持移动语义,C++11中引入了move语义,使得unique_ptr对象可以被移动而不是被复制。 unique_ptr的优点是可移植性好,可以避免循环引用的问题,缺点是不能共享资源。 ## weak_ptr weak_ptr是一种弱引用智能指针,它的实现原理是与shared_ptr配合使用。weak_ptr不会增加引用计数,它只是提供了对所指向对象的一个非拥有性的访问。当所指向的对象被销毁后,weak_ptr将自动失效。 weak_ptr的优点是可以避免循环引用的问题,缺点是不能访问所指向对象的成员变量和成员函数。如果需要访问所指向对象的成员变量和成员函数,需要将weak_ptr转换为shared_ptr。 下面是一个示例代码,展示了shared_ptrunique_ptrweak_ptr的使用方式: ```c++ #include <iostream> #include <memory> class A { public: A() { std::cout << "A()" << std::endl; } ~A() { std::cout << "~A()" << std::endl; } void foo() { std::cout << "foo()" << std::endl; } }; int main() { // shared_ptr std::shared_ptr<A> p1(new A()); std::shared_ptr<A> p2(p1); std::cout << "p1.use_count() = " << p1.use_count() << std::endl; std::cout << "p2.use_count() = " << p2.use_count() << std::endl; // unique_ptr std::unique_ptr<A> p3(new A()); // std::unique_ptr<A> p4(p3); // error: copy constructor is deleted std::unique_ptr<A> p5(std::move(p3)); if (p3 == nullptr) { std::cout << "p3 is nullptr" << std::endl; } // weak_ptr std::shared_ptr<A> p6(new A()); std::weak_ptr<A> p7(p6); std::cout << "p7.use_count() = " << p7.use_count() << std::endl; if (auto p8 = p7.lock()) { p8->foo(); } else { std::cout << "p7 is expired" << std::endl; } return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码到程攻

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

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

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

打赏作者

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

抵扣说明:

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

余额充值