C++11 智能指针:unique_ptr、shared_ptr和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_unique std::unique_ptrnew
内存分配 高效,单一操作分配内存 初始化和分配是分离的操作
异常安全 更安全,避免资源泄漏 可能因异常导致内存泄漏
代码简洁性 更简洁,无需手动使用 new 手动使用 new,代码更冗长
自定义删除器 无法直接使用自定义删除器 支持自定义删除器

示例

C++ 复制代码
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;
}

模拟实现

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

模拟实现

C++ 复制代码
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 不影响引用计数,可作为一种弱引用解决此问题。

C++ 复制代码
#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;
}
相关推荐
go54631584651 小时前
本地部署 GitHub 上的 Python 人脸识别项目
开发语言·python·github
wjs20242 小时前
PHP 连接 Memcached 服务
开发语言
溟洵2 小时前
【C/C++】理解C++内存与Linux虚拟地址空间的关系---带你通透C++中所有数据
linux·c语言·c++
十八朵郁金香4 小时前
深入理解 JavaScript 中的 this 指向
开发语言·前端·javascript
一只自律的鸡4 小时前
C进阶 自定义类型
c语言·开发语言
让学习成为一种生活方式4 小时前
libGL.so.1: cannot open shared object file: No such file or directory-linux022
linux·开发语言·python
贵州晓智信息科技4 小时前
使用 Three.js 转换 GLSL 粒子效果着色器
开发语言·javascript·着色器
一只蜗牛儿5 小时前
C# 版 OpenCV:OpenCVSharp 最详细最全面教程(万字详细总结)
开发语言·opencv·c#
辰阳星宇5 小时前
203、【数组】NLP分词实现(Python)
开发语言·python