当前位置: 首页 > news >正文

C++11 智能指针:unique_ptr、shared_ptr和weak_ptr 功能特性 模拟实现

文章目录

  • unique_ptr
    • 功能和特性
    • 使用场景
    • make_unique
    • 模拟实现
  • shared_ptr
    • 功能和特性
    • 使用场景
    • make_shared
    • 模拟实现
  • weak_ptr

C++ 中智能指针都是 RAII(Resource Acquisition Is Initialization)机制的典型应用,在构造时获取资源,在析构时释放资源,将资源管理与对象的生命周期绑定,使得资源管理更加直观和可靠。

unique_ptr

std::unique_ptr 的主要作用是对动态分配的资源进行严格的独占式管理,确保在其生命周期结束时自动释放所管理的资源,从而防止内存泄漏,让资源的生命周期与 std::unique_ptr 变量的生命周期紧密绑定,无需手动调用 delete 操作符,使资源管理更安全、更高效。

功能和特性

  • 独占所有权:同一时刻只能有一个 std::unique_ptr 指向给定的资源,保证了资源的独占访问,避免多个指针同时操作同一资源带来的冲突和数据不一致问题。
  • 移动语义:支持移动构造和移动赋值操作,允许将资源的所有权从一个 std::unique_ptr 转移到另一个 std::unique_ptr,但不支持复制操作,提高了资源转移的效率,避免了不必要的资源复制。
  • 自动释放资源:当 std::unique_ptr 对象超出作用域或被显式销毁时,会自动调用其析构函数来释放所管理的资源,无需手动释放,降低了忘记释放资源导致内存泄漏的风险。
  • 可定制删除器:可以通过模板参数指定自定义的删除器,用于定义释放资源的特定方式,以满足不同资源类型的特殊释放需求。
  • 空指针值:可以通过默认构造函数创建一个空的 std::unique_ptr,表示不管理任何资源,也可以通过将其赋值为 nullptr 来使其变为空指针。

使用场景

  • 函数内部局部资源管理:在函数内部动态分配资源时,使用 std::unique_ptr 来管理这些资源,确保函数执行结束时资源能被正确释放,例如在函数中动态分配数组或对象。
  • 资源独占场景:当某个资源只需要被一个对象独占使用时,如一个文件操作类中使用 std::unique_ptr 来管理文件句柄,保证同一时间只有该对象能访问文件。
  • 作为函数返回值:函数可以返回一个 std::unique_ptr,将动态分配资源的所有权安全地转移给调用者,调用者可以继续管理该资源,而无需担心资源的释放问题。
  • 管理动态分配的数组:可以使用 std::unique_ptr 来管理动态分配的数组,通过指定数组删除器来确保数组内存的正确释放。

make_unique

std::make_unique 是 C++14 引入的一个辅助函数,用于创建并返回 std::unique_ptr 对象。它在动态分配内存时,提供了更简洁和安全的方式,与 std::unique_ptr 的构造配合使用,减少了潜在的错误风险。

特点std::make_uniquestd::unique_ptrnew
内存分配高效,单一操作分配内存初始化和分配是分离的操作
异常安全更安全,避免资源泄漏可能因异常导致内存泄漏
代码简洁性更简洁,无需手动使用 new手动使用 new,代码更冗长
自定义删除器无法直接使用自定义删除器支持自定义删除器

示例

int main() {
    // 使用 unique_ptr 管理一个 vector<int>
    auto vecPtr = std::make_unique<std::vector<int>>();

    vecPtr->push_back(10);
    vecPtr->push_back(20);
    vecPtr->push_back(30);

    std::cout << "Vector elements: ";
    for (const auto& elem : *vecPtr) {
        std::cout << elem << " ";
    }
    std::cout << "\n";

    // vecPtr2 超出作用域时,资源会自动释放
    return 0;
}

模拟实现

template <typename T>
class UniquePtr {
public:
    explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {}
    
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr<T>& operator=(const UniquePtr&) = delete;
    
    UniquePtr(UniquePtr&& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }
    
    UniquePtr& operator=(UniquePtr&& other) noexcept {
        if (this != &other) {
            delete ptr_;
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }
    
    ~UniquePtr() { delete ptr_; }
    
    T& operator*() { return *ptr_; }
    T* operator->() { return ptr_; }
    
    // 获取原始指针
    T* get() const { return ptr_; }
    
    // 重置指针
    void reset(T* ptr = nullptr) {
        if (ptr != ptr_) {
            delete ptr_;
            ptr_ = ptr;
        }
    }
    
    // 释放所有权
    T* release() {
        T* tmp = ptr_;
        ptr_ = nullptr;
        return tmp;
    }

private:
    T* ptr_;
};

shared_ptr

std::shared_ptr 的主要作用是实现资源的共享所有权。多个 std::shared_ptr 可以指向同一个对象,通过引用计数机制来跟踪有多少个 std::shared_ptr 共享该对象。当最后一个指向该对象的 std::shared_ptr 被销毁或重置时,对象的内存会被自动释放,从而避免了内存泄漏。

std::shared_ptr 可能会导致循环引用问题,即两个或多个 std::shared_ptr 相互引用,使得引用计数永远不会变为 0,从而导致内存泄漏。为了解决这个问题,可以使用 std::weak_ptr,它是一种弱引用,不会增加引用计数。

功能和特性

  • 引用计数std::shared_ptr 内部维护一个引用计数,记录有多少个 std::shared_ptr 共享同一个对象。每当一个新的 std::shared_ptr 指向该对象时,引用计数加 1;当一个 std::shared_ptr 被销毁或重置时,引用计数减 1。当引用计数变为 0 时,对象的内存会被自动释放。
  • 共享所有权:多个 std::shared_ptr 可以同时拥有同一个对象的所有权,这使得资源可以在多个地方被安全地使用,而不用担心资源过早释放或重复释放的问题。
  • 自动资源管理std::shared_ptr 会在引用计数变为 0 时自动释放所管理的资源,无需手动调用 delete 操作符,提高了代码的安全性和可维护性。
  • 可复制和赋值std::shared_ptr 支持复制构造和赋值操作,复制或赋值操作会增加引用计数,确保资源的共享和正确管理。
  • 自定义删除器:可以通过模板参数指定自定义的删除器,用于定义释放资源的特定方式,以满足不同资源类型的特殊释放需求。

使用场景

  • 多个对象共享资源:当多个对象需要同时访问和使用同一个资源时,使用 std::shared_ptr 可以方便地实现资源的共享。例如,多个线程可能需要访问同一个数据结构,使用 std::shared_ptr 可以确保该数据结构在所有线程都不再使用时才被释放。
  • 实现对象池:在对象池的实现中,std::shared_ptr 可以用于管理对象的生命周期。当对象从对象池中取出时,使用 std::shared_ptr 管理该对象;当对象被放回对象池或不再使用时,std::shared_ptr 会自动释放对象的内存。

make_shared

std::make_shared 是 C++11 中引入的一个用于创建 std::shared_ptr 对象的函数模板。它通过单次内存分配同时创建被管理的对象和控制块(control block),从而提高效率并减少潜在的内存碎片。

特点std::make_sharedshared_ptrnew
内存分配一次分配,控制块和对象共享内存。两次分配,控制块和对象分开存储。
异常安全构造失败无内存泄漏(更安全)。构造失败时可能导致对象泄漏。
代码简洁性简洁,无需显式 new需要显式使用 new,易出错。
自定义删除器不支持自定义删除器。支持自定义删除器(适合特殊资源管理)。
auto sp = std::make_shared<int>(9); // 同时分配对象和控制块

模拟实现

template <typename T>
class SharedPtr {
public:
    explicit SharedPtr(T* ptr = nullptr) 
        : ptr_(ptr), ref_count_(ptr ? new std::atomic<size_t>(1) : nullptr) {}
    
    SharedPtr(const SharedPtr& other) : ptr_(other.ptr_), ref_count_(other.ref_count_) {
        if (ref_count_)
            ++(*ref_count_);
    }
    
    SharedPtr& operator=(const SharedPtr& other) {
        if (this != other) {
            release();
            ptr_ = other.ptr_;
            ref_count_ = other.ref_count_;
            if (ref_count_)
                	ref_count_->fetch_a;
        }
        return *this;
    }
    
    T& operator*() { return *ptr_; }
    T* operator->() { return ptr_; }
    
    ~SharedPtr() { release(); }
    
    T* get() const { return ptr_; }
    
    int use_count() const {
        return ref_count_ ? ref_count_->load() : 0;
    }
    
private:
    void release() {
        if (ref_count_ && ref_count_->fetch_sub(1) == 0) {
            delete ptr_;
            delete ref_count_;
        }
    }
    
    T* ptr_;
    std::atomic<size_t>* ref_count_;
};

weak_ptr

std::weak_ptr 是 C++ 标准库中的一种智能指针,它是为了配合 std::shared_ptr 而引入的,用于解决 std::shared_ptr 可能存在的循环引用。当多个 std::shared_ptr 相互引用形成循环时,它们的引用计数永远不会降为 0,导致对象无法被释放,而 std::weak_ptr 不影响引用计数,可作为一种弱引用解决此问题。

#include <iostream>
#include <memory>

class B; // 前向声明

class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() { std::cout << "A destroyed\n"; }
};

class B {
public:
    std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用
    ~B() { std::cout << "B destroyed\n"; }
};

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;

    // 此时 a 和 b 的引用计数都为 1,不会造成循环引用
    std::cout << "a use_count: " << a.use_count() << "\n"; // 输出 1
    std::cout << "b use_count: " << b.use_count() << "\n"; // 输出 1

    return 0;
}

相关文章:

  • 【新手入门】SQL注入之盲注
  • 双机热备旁挂组网场景实验
  • A64指令集基本指令(一):分支指令
  • Apache Doris:一款高性能的实时数据仓库
  • 实现 Leaflet 多类型点位标记与聚合功能的实战经验分享
  • python-leetcode-最长有效括号
  • 蓝桥杯15届JavaB组6题
  • C/C++语言知识点一
  • 提示学习(Prompting)
  • 算法与数据结构(二叉树中的最大路径和)
  • 深入了解 Python 中的 MRO(方法解析顺序)
  • Docker搭建基于Rust语言的云原生可观测平台OpenObserve
  • sklearn中的决策树-分类树:剪枝参数
  • PMP项目管理—整合管理篇—7.结束项目或阶段
  • 【Kubernetes】API server 限流 之 maxinflight.go
  • 跨AWS账户共享SQS队列以实现消息传递
  • SQL Server 视图的更新排查及清除缓存
  • Protobuf原理与序列化
  • 高数1.1 函数
  • 深度学习-11.用于自然语言处理的循环神经网络
  • 广州网站建设88/有广告位怎么找广告商
  • 怎么查网站是不是百度做的/西安今日头条最新新闻
  • 昌平网站设计/不错宁波seo公司
  • 青岛提供网站建设哪家便宜/聊城疫情最新消息
  • 哈尔滨做网站需要多少钱/360seo关键词优化
  • 网站如何做cc防护/好的推广方式