C++11智能指针详解

目录

一、智能指针概述

二、智能指针的背景

三、std::auto_ptr(在C++11已废弃)

(一) 介绍

(二) 模拟实现

(三)特点

(四)缺点

(五)使用场景

 四、std::unique_ptr

(一)介绍

(二)模拟实现

(三)特点

(四)使用场景

五、std::shared_ptr

(一)介绍

(二)模拟实现

(三)特点

(四)使用场景

六、std::weak_ptr

(一)介绍

(二)模拟实现

(三)特点

(四)使用场景

七、智能指针的注意事项

(一)避免混用

(二)使用std::make_shared和std::make_unique

(三)注意性能

六、总结


一、智能指针概述

在C++编程中,内存管理一直是一个重要且容易出错的环节。C++11引入了智能指针的概念,利用对象的生命周期来管理资源,构造函数获取资源,析构函数释放资源,基于RAII机制实现了自动内存管理。本文将详细介绍C++11中的三种主要智能指针:std::unique_ptrstd::shared_ptrstd::weak_ptr,并探讨它们的使用场景和注意事项。

二、智能指针的背景

在C++中,动态内存分配通常通过new关键字完成,而释放内存则需要使用delete。然而,这种手动管理内存的方式容易引发多种问题,例如:

  1. 内存泄漏:忘记调用delete,导致分配的内存无法回收。

  2. 野指针:释放内存后,仍然使用指向已释放内存的指针。

  3. 重复释放:多次调用delete释放同一块内存,导致未定义行为。

为了解决这些问题,C++11引入了智能指针,它们通过RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制自动管理内存,从而避免了上述问题。

三、std::auto_ptr(在C++11已废弃)

(一) 介绍

std::auto_ptr是C++98标准中引入的一种智能指针,用于管理动态分配的对象。它通过独占所有权的方式管理对象,当std::auto_ptr超出作用域时,它会自动释放其管理的对象。

(二) 模拟实现

template <typename T>
class auto_ptr {
private:
    T* ptr; // 指向动态分配的对象

public:
    // 构造函数
    auto_ptr(T* p = nullptr) : ptr(p) {}

    // 拷贝构造函数(所有权转移)
    auto_ptr(auto_ptr& other) : ptr(other.ptr) {
        other.ptr = nullptr; // 转移所有权
    }

    // 拷贝赋值操作(所有权转移)
    auto_ptr& operator=(auto_ptr& other) {
        if (this != &other) {
            delete ptr; // 释放当前管理的对象
            ptr = other.ptr; // 转移所有权
            other.ptr = nullptr;
        }
        return *this;
    }

    // 析构函数
    ~auto_ptr() {
        delete ptr; // 释放管理的对象
    }

    // 重载解引用操作符
    T& operator*() const { return *ptr; }

    // 重载箭头操作符
    T* operator->() const { return ptr; }

    // 释放所有权
    void release() {
        ptr = nullptr;
    }

    // 交换两个 auto_ptr 的所有权
    void swap(auto_ptr& other) {
        std::swap(ptr, other.ptr);
    }

    // 获取当前管理的指针
    T* get() const { return ptr; }

    // 重置指针
    void reset(T* p = nullptr) {
        if (ptr != p) {
            delete ptr; // 释放当前管理的对象
            ptr = p;
        }
    }
};

(三)特点

  • 独占所有权std::auto_ptr不允许复制,但可以移动。这意味着你不能有两个std::auto_ptr同时指向同一个对象。

  • 自动释放:当std::auto_ptr超出作用域时,它会自动调用delete释放其管理的对象。

  • 轻量级std::auto_ptr的实现非常轻量级,几乎不增加额外的性能开销。

(四)缺点

  • 不支持数组std::auto_ptr不能用于管理动态分配的数组。

  • 不支持自定义删除器std::auto_ptr不支持自定义删除器,这在某些情况下会限制其灵活性。

  • 不支持移动语义std::auto_ptr的复制构造函数和赋值操作符会转移所有权,而不是真正地“移动”对象,这在C++11的移动语义中显得不够自然。

(五)使用场景

std::auto_ptr在C++98中被广泛使用,但在C++11中已经被废弃,建议使用std::unique_ptr替代。

 四、std::unique_ptr

(一)介绍

std::unique_ptr是C++11引入的现代智能指针,用 于独占式管理动态分配的内存。std::unique_ptr 支持移动语义,但不支持拷贝语义。这意味着 std::unique_ptr不能被拷贝,只能被移动。支持了数组和自定义删除器。
模拟实现思路 指针:用对象的生命周期管理指针。 独占式:拷贝构造函数和拷贝赋值运算符被禁用(= delete),防止多个 unique_ptr 同时管理同一个对象。 支持移动:移动构造函数和移动赋值运算符转移所有权。 自动释放:在析构函数中释放对象。

(二)模拟实现

template <typename T>
class unique_ptr {
private:
    T* ptr; // 存储指向对象的指针

    // 私有释放函数
    void release() {
        if (ptr) {
            delete ptr; // 释放对象
            ptr = nullptr; // 置空指针
        }
    }

public:
    // 构造函数
    explicit unique_ptr(T* p = nullptr) : ptr(p) {}

    // 禁用拷贝构造和拷贝赋值
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

    // 移动构造
    unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr; // 原对象置空
    }

    // 移动赋值
    unique_ptr& operator=(unique_ptr&& other) noexcept {
        if (this != &other) {
            release(); // 释放当前对象
            ptr = other.ptr; // 转移所有权
            other.ptr = nullptr; // 原对象置空
        }
        return *this;
    }

    // 析构函数
    ~unique_ptr() {
        release(); // 自动释放对象
    }

    // 重载解引用操作符
    T& operator*() const { return *ptr; }

    // 重载箭头操作符
    T* operator->() const { return ptr; }
};

(三)特点

  • 独占所有权std::unique_ptr不允许复制,但可以移动。这意味着你不能有两个std::unique_ptr同时指向同一个对象。

  • 自动释放:当std::unique_ptr超出作用域时,它会自动调用delete释放其管理的对象。

  • 轻量级std::unique_ptr的实现非常轻量级,几乎不增加额外的性能开销。

(四)使用场景

  • 当你需要独占一个对象的所有权时,例如在单线程环境中管理动态分配的资源。

  • 当你需要将动态分配的对象作为函数返回值时,std::unique_ptr是一个很好的选择。

五、std::shared_ptr

(一)介绍

std::shared_ptr是C++11引入的智能指针,用于共享式管理动态分配的内存。它允许多个 std::shared_ptr实例共享同一个对象的所有权。 当最后一个std::shared_ptr被析构时,它会自动释放所管理的内存。 每个std::shared_ptr实例 都会维护一个引用计数器,当引用计数器变为零时,对象会被自动释放。可以通过模板参数指定自定 义删除器,引用计数操作是线程安全的,因此 std::shared_ptr可以在多线程环境中安全使用。
模拟实现思路 引用计数:通过一个计数器记录有多少个 shared_ptr 指向同一个对象。 自动释放:当最后一个 shared_ptr 被销毁时,释放对象的内存。 拷贝构造和赋值操作:通过引用计数的增加和减少来管理对象的生命周期。

(二)模拟实现

template <typename T>
class shared_ptr {
private:
    T* ptr; // 指向管理的对象
    int* count; // 引用计数器

    // 私有辅助函数:释放资源
    void release() {
        if (ptr && --(*count) == 0) {
            delete ptr; // 释放对象
            delete count; // 释放计数器
        }
    }

public:
    // 构造函数
    explicit shared_ptr(T* p = nullptr) : ptr(p), count(new int(1)) {
        if (!ptr) {
            *count = 0; // 如果指针为空,计数器置为0
        }
    }

    // 拷贝构造函数
    shared_ptr(const shared_ptr& other) : ptr(other.ptr), count(other.count) {
        ++(*count); // 增加引用计数
    }

    // 赋值运算符
    shared_ptr& operator=(const shared_ptr& other) {
        if (this != &other) {
            release(); // 释放当前对象
            ptr = other.ptr;
            count = other.count;
            ++(*count); // 增加引用计数
        }
        return *this;
    }

    // 析构函数
    ~shared_ptr() {
        release(); // 释放资源
    }

    // 重载解引用操作符
    T& operator*() const {
        if (!ptr) throw std::runtime_error("Dereferencing null pointer");
        return *ptr;
    }

    // 重载箭头操作符
    T* operator->() const {
        if (!ptr) throw std::runtime_error("Accessing null pointer");
        return ptr;
    }

(三)特点

  • 共享所有权:多个std::shared_ptr可以共享同一个对象的所有权。

  • 自动释放:当最后一个std::shared_ptr被销毁时,它会自动调用delete释放其管理的对象。

  • 线程安全std::shared_ptr的引用计数是线程安全的,适合在多线程环境中使用。

(四)使用场景

  • 当你需要在多个对象之间共享一个资源时,例如在多线程环境中共享一个动态分配的对象。

  • 当你需要将动态分配的对象作为函数参数传递时,std::shared_ptr可以方便地共享所有权。

六、std::weak_ptr

(一)介绍

std::weak_ptr是C++11引入的一种特殊的智能指 针,用于解决std::shared_ptr的循环引用问题。 它允许一个对象安全地引用另一个对象,但不会影响对象的引用计数。std::weak_ptr提供了一个弱引用,指向由std::shared_ptr管理的 对象。它不会增加引用计数,因此不会阻止对象的析构。通过std::weak_ptr,可以打破 std::shared_ptr之间的循环引用,避免内存泄漏。与std::shared_ptr类似,std::weak_ptr 的操作也是线程安全的。

模拟实现 weak_ptr依赖于shared_ptr,与 shared_ptr 共享同一个引用计数器 除了 shared_ptr 的强引用计数,还需要一个弱引用计数,记录有多少个 weak_ptr 指向同一个对象。 提供 lock() 方法,返回一个 shared_ptr,用于安全地访问对象。 提供 expired() 方法,检查对象是否已经被销毁。

(二)模拟实现

template <typename T>
class weak_ptr {
private:
    T* ptr;           // 指向被管理的对象
    RefCounter* counter;  // 指向引用计数器

public:
    // 默认构造函数,初始化为空的 weak_ptr
    weak_ptr() : ptr(nullptr), counter(nullptr) {}

    // 从 shared_ptr 构造 weak_ptr
    weak_ptr(const shared_ptr<T>& sp) : ptr(sp.ptr), counter(sp.counter) {
        ++(*counter->weak_count);  // 增加弱引用计数
    }

    // 拷贝构造函数
    weak_ptr(const weak_ptr& wp) : ptr(wp.ptr), counter(wp.counter) {
        ++(*counter->weak_count);  // 增加弱引用计数
    }

    // 赋值运算符
    weak_ptr& operator=(const weak_ptr& wp) {
        if (this != &wp) {  // 避免自赋值
            if (--(*counter->weak_count) == 0) {  // 减少当前对象的弱引用计数
                delete counter;  // 如果弱引用计数为 0,删除引用计数器
            }
            ptr = wp.ptr;  // 复制指针
            counter = wp.counter;  // 复制引用计数器
            ++(*counter->weak_count);  // 增加新对象的弱引用计数
        }
        return *this;
    }

    // 析构函数
    ~weak_ptr() {
        if (--(*counter->weak_count) == 0) {  // 减少弱引用计数
            delete counter;  // 如果弱引用计数为 0,删除引用计数器
        }
    }

    // 尝试将 weak_ptr 转换为 shared_ptr
    shared_ptr<T> lock() const {
        if (*counter->use_count > 0) {  // 如果对象仍然存在(强引用计数大于 0)
            return shared_ptr<T>(ptr);  // 返回一个 shared_ptr
        } else {
            return shared_ptr<T>();  // 否则返回空的 shared_ptr
        }
    }

    // 检查对象是否已经失效(即没有强引用指向它)
    bool expired() const {
        return *counter->use_count == 0;  // 如果强引用计数为 0,返回 true
    }
};

(三)特点

  • 弱引用std::weak_ptr不会增加对象的引用计数,因此不会阻止对象的销毁。

  • 循环引用:两个或多个对象相互引用,导致它们无法被垃圾回收或自动销毁的情况。

  • 解决循环引用std::weak_ptr可以用于打破std::shared_ptr之间的循环引用,避免内存泄漏。

  • 线程安全std::weak_ptr的操作也是线程安全的。

(四)使用场景

  • 当你需要在std::shared_ptr之间建立关系,但又不想增加引用计数时,例如在双向链表或观察者模式中。

  • 当你需要访问一个可能已经被销毁的对象时,std::weak_ptr可以安全地检查对象是否仍然存在。

七、智能指针的注意事项

(一)避免混用

虽然std::unique_ptrstd::shared_ptr都可以管理动态分配的对象,但它们的语义不同。尽量避免在同一个代码库中混用这两种智能指针,以免造成混淆。

(二)使用std::make_sharedstd::make_unique

C++11提供了std::make_shared和C++14提供了std::make_unique,它们可以更安全地创建std::shared_ptrstd::unique_ptr,避免了直接使用new可能带来的异常安全问题。

(三)注意性能

虽然智能指针简化了内存管理,但它们也引入了一定的性能开销。std::shared_ptr的引用计数操作可能会导致额外的性能损耗,尤其是在高并发场景下。如果性能是关键因素,建议优先使用std::unique_ptr

六、总结

C++11的智能指针是现代C++编程中不可或缺的一部分。它们通过自动管理内存,极大地简化了程序员的工作,同时提高了代码的安全性和可维护性。std::unique_ptr适用于独占所有权的场景,std::shared_ptr适用于共享所有权的场景,而std::weak_ptr则用于解决循环引用问题。合理使用这些智能指针,可以让你的C++代码更加健壮和高效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值