C++四种智能指针

1. unique_ptr(独占式智能指针)

一个指针管理一个对象,并确保这个对象只能由此一个指针负责,一旦离开作用域或者指针被销毁,那指针会自动把管理的资源统一销毁。

1.1 特性

  • 独占所有权:一块内存/一个对象只能由一个unique_ptr指针访问/管理。

  • 不支持拷贝构造和拷贝赋值(删除了拷贝操作)。

  • 支持移动语义(把所有权移交给另一个unique_ptr指针),可以通过 std::move() 转移所有权。

  • 轻量、高效,几乎无运行时开销,性能与原始指针基本相同。

  • 自动管理内存,析构时自动调用delete或自定义的删除器释放资源,防止内存泄漏。

  • 适用于资源的唯一拥有者,包括函数返回、工厂模式等场景。

  • 遵循RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则,异常安全。

1.2 缺点

  • 不支持共享所有权,当需要多个指针访问同一对象时无能为力。
  • 不能存储在需要拷贝语义的容器中(如std::vector,但可通过移动语义解决)。
  • 从函数获取所有权后,原指针对象为空(需要明确所有权转移的语义)。
  • 在特定场景下,移动语义可能带来代码复杂度。

1.3 底层实现

std::unique_ptr<T> 本质上是一个模板类,内部主要包含两个成员:

  • 一个指向所管理对象的原始指针T* ptr)。
  • 一个删除器(Deleter)函数对象(默认是 std::default_delete<T>,相当于 delete 操作)。

为了保证“独占性”,unique_ptr 显式删除了拷贝构造函数和拷贝赋值运算符。

unique_ptr 提供了移动构造函数和移动赋值运算符,用于转移所有权,移动时,源指针将其托管的原始指针转移给目标,并将自身置为nullptr,实现所有权移交。

template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
    T* ptr;          // 指向管理对象的原始指针
    Deleter del;     // 删除器,用于释放资源(默认为 delete)

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

    // 带自定义删除器的构造
    unique_ptr(T* p, const Deleter& d) noexcept : ptr(p), del(d) {}

    // --- 禁止拷贝:确保独占性 ---
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

    // --- 移动构造函数 ---
    // 将 other 的资源接管,other 自身置空
    unique_ptr(unique_ptr&& other) noexcept
        : ptr(other.ptr)
        , del(std::forward<Deleter>(other.del))
    {
        other.ptr = nullptr;  // 关键:移交后原指针为空
    }

    // --- 移动赋值运算符 ---
    unique_ptr& operator=(unique_ptr&& other) noexcept {
        if (this != &other) {
            reset();  // 先释放当前资源
            ptr = other.ptr;
            del = std::forward<Deleter>(other.del);
            other.ptr = nullptr;
        }
        return *this;
    }

    // --- 析构函数 ---
    // 自动释放资源(如果指针非空)
    ~unique_ptr() {
        if (ptr) {
            del(ptr);  // 调用删除器(默认为 delete)
        }
    }

    // --- 核心接口 ---
    // 释放当前资源,接管新指针
    void reset(T* p = nullptr) noexcept {
        if (ptr) {
            del(ptr);
        }
        ptr = p;
    }

    // 放弃所有权,返回原始指针
    T* release() noexcept {
        T* old_ptr = ptr;
        ptr = nullptr;
        return old_ptr;
    }

    // 访问内部指针
    T* get() const noexcept { return ptr; }
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }

    // 布尔转换:判断指针是否有效
    explicit operator bool() const noexcept {
        return ptr != nullptr;
    }
};

std::move()的底层实现:

std::move移动语义的触发器,它本身并不执行移动操作,而是将一个左值“转换”为右值引用,从而允许调用移动构造函数或移动赋值函数。

template
constexpr std::remove_reference_t&& move(T&& t) noexcept {
    return static_cast&&>(t);
}

1.4 使用示例

推荐使用 std::make_unique<T>(...) 创建指针(C++14 起支持)。

std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();

#include <memory>
#include <iostream>

int main() {
    std::unique_ptr<int> ptr1 = std::make_unique<int>(100);

    // 移动所有权
    std::unique_ptr<int> ptr2 = std::move(ptr1);

    if (ptr1 == nullptr) {
        std::cout << "ptr1 is null" << std::endl;
    }
    std::cout << *ptr2 << std::endl;

    return 0;
}


2. shared_ptr(共享式智能指针)

一个智能指针,允许多个指针共享同一个对象的所有权,内部通过引用计数机制管理资源。当最后一个shared_ptr被销毁或重置时,才会释放所管理的对象。

2.1 特性

  • 共享所有权:多个shared_ptr可以指向同一对象,共享对资源的管理权,灵活性高。

  • 使用引用计数(reference count)追踪有多少个shared_ptr指向同一个对象。

  • 每增加一个shared_ptr实例,引用计数加一;每销毁一个,引用计数减一。

  • 当引用计数变为 0 时,自动调用删除器(默认为delete)释放资源,避免内存泄漏。

  • 支持跨函数、跨模块传递所有权,适合复杂对象生命周期管理。

  • 支持拷贝构造和拷贝赋值,也支持移动语义。

  • 可以通过use_count()查看当前引用计数,unique()判断是否唯一拥有。

  • 支持自定义删除器(deleter),可用于特殊资源释放逻辑(如关闭文件句柄、释放数组等)。

  • 是线程安全的控制块(引用计数部分),但指向对象的访问仍需同步。

  • 可与标准容器(如std::vector<std::shared_ptr<T>>)良好配合。

  • 遵循 RAII 原则,异常安全。

2.2 缺点

  • 有性能开销:维护引用计数需要额外空间和原子操作(多线程下)。
  • 存在循环引用问题(A 持有 B 的shared_ptr,B 也持有 A 的 shared_ptr,导致引用计数永不归零)。
  • 不适用于性能敏感场景(相比 unique_ptr 更重)。
  • 构造时若分开写 newshared_ptr 初始化,可能引发异常不安全问题(应优先使用 make_shared)。

2.3 底层实现

std::shared_ptr<T> ,其核心机制是引用计数(Reference Counting)。它本质上是一个模板类,内部包含两个关键指针:

  • 资源指针(T\* ptr:指向被管理的实际对象。
  • 控制块指针(Control Block):指向一个动态分配的控制块,其中包含:
    • 引用计数(shared count):记录当前有多少个 shared_ptr 共享该对象。
    • 弱计数(weak count):记录 weak_ptr 的数量。
    • 删除器(Deleter):用于释放对象资源。
    • 自定义分配器(Allocator)(可选)。

当第一个 shared_ptr 创建时,会同时分配对象和控制块。后续拷贝的 shared_ptr 都共享同一个控制块,每次拷贝会递增引用计数,销毁时递减。当引用计数降为 0 时,调用删除器释放对象。

shared_ptr 的拷贝构造和赋值操作会增加引用计数,析构时会减少引用计数,确保资源在最后一个 shared_ptr 离开作用域时自动释放。

template<typename T>
class shared_ptr {
    T* ptr;                      // 指向管理的对象
    control_block* control;      // 指向控制块(含引用计数、删除器等)

public:
    // 支持拷贝:增加引用计数
    shared_ptr(const shared_ptr& other) noexcept 
        : ptr(other.ptr), control(other.control) {
        if (control) {
            ++control->shared_count;
        }
    }

    // 支持移动:转移指针,不改变引用计数
    shared_ptr(shared_ptr&& other) noexcept 
        : ptr(other.ptr), control(other.control) {
        other.ptr = nullptr;
        other.control = nullptr;
    }

    // 析构:减少引用计数,若为0则释放资源
    ~shared_ptr() {
        if (control && --control->shared_count == 0) {
            control->deleter(ptr);  // 释放对象
            // 检查 weak_count,若也为0,释放控制块自身
            if (--control->weak_count == 0) {
                delete control;
            }
        }
    }
    // ...
};

2.4 使用示例

推荐使用 std::make_shared<T>(...) 创建指针(更高效且异常安全):

std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();

#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(200);
    std::cout << "引用计数: " << ptr1.use_count() << std::endl; // 1

    {
        std::shared_ptr<int> ptr2 = ptr1;
        std::cout << "引用计数: " << ptr1.use_count() << std::endl; // 2
    }

    std::cout << "引用计数: " << ptr1.use_count() << std::endl; // 1

    return 0;
}


3. weak_ptr(弱引用智能指针)

一种不拥有对象所有权的智能指针,通常与 shared_ptr 配合使用,用于解决 shared_ptr循环引用问题。

3.1 特性

  • 不拥有所有权,不增加引用计数,仅“观察”由 shared_ptr 管理的对象,提供对资源的安全“弱访问”,避免悬挂指针。
  • 打破 shared_ptr 循环引用,防止内存泄漏。
  • 高效且轻量,控制块与 shared_ptr 共享。
  • 必须从shared_ptr或另一个weak_ptr构造。
  • 用于临时访问 shared_ptr 所管理的对象,但不影响其生命周期。
  • 必须通过 lock() 转换为 shared_ptr 才能安全访问对象。
  • 若原始对象已被释放,则 lock() 返回空 shared_ptr
  • 不支持直接解引用(如 ->*),必须升级为 shared_ptr
  • 常用于缓存、观察者模式、监听器等场景。

3.2 缺点

  • 不能直接访问对象,访问前必须调用 lock()
  • lock() 后仍需检查是否为空(可能对象已销毁)。
  • 引入额外的逻辑复杂度,在简单场景中没必要使用。

3.3 底层实现

std::weak_ptr<T> 底层结构与 shared_ptr 类似,也包含:

  • 资源指针(T\* ptr:直接指向对象(用于快速访问)。
  • 控制块指针(control_block\*:指向同一个控制块,但只递增弱计数weak_count),不增加引用计数。

weak_ptr 本身不能直接访问对象,必须通过 lock() 方法尝试获取一个 shared_ptrlock() 会检查控制块中的引用计数:

  • 如果对象仍存在(shared_count > 0),则返回一个有效的 shared_ptr,并增加引用计数。
  • 如果对象已被释放(shared_count == 0),则返回空 shared_ptr
template<typename T>
class weak_ptr {
    T* ptr;
    control_block* control;

public:
    // 构造:从 shared_ptr 或 weak_ptr 构造,只增加 weak_count
    weak_ptr(const shared_ptr<T>& sp) noexcept 
        : ptr(sp.ptr), control(sp.control) {
        if (control) {
            ++control->weak_count;
        }
    }

    // lock():尝试获取 shared_ptr
    shared_ptr<T> lock() const noexcept {
        if (control && control->shared_count > 0) {
            // 原子操作递增 shared_count,防止对象被释放
            ++control->shared_count;
            return shared_ptr<T>(ptr, control);  // 返回 shared_ptr
        }
        return shared_ptr<T>();  // 空 shared_ptr
    }

    ~weak_ptr() {
        if (control && --control->weak_count == 0) {
            // 如果 weak_count 降为 0,且 shared_count 也为 0,则释放控制块
            if (control->shared_count == 0) {
                delete control;
            }
        }
    }
    // ...
};

3.4 使用示例

配合 shared_ptr 使用,解决循环引用问题:

#include <memory>
#include <iostream>

struct Node {
    std::shared_ptr<Node> next;
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    auto a = std::make_shared<Node>();
    auto b = std::make_shared<Node>();
    a->next = b;
    b->next = a; // 循环引用,无法释放!

    return 0;
}
// 上述代码会导致内存泄漏,因为引用计数永远不为 0。std::shared_ptr<MyClass> shared = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weak = shared;

// 在需要时尝试获取访问权
if (auto locked = weak.lock()) {
    locked->doSomething(); // 安全访问
} else {
    std::cout << "对象已释放。\n";
}


4. auto_ptr(已弃用)

C++98 引入的早期智能指针,用于自动管理动态分配的对象,但在 C++11 中被弃用,在 C++17 中被移除。

4.1 特性

  • 表面上实现自动释放资源(RAII),在析构时自动 delete 所管理的对象。

  • 所有权转移语义:拷贝或赋值时会将原指针置为空(即“转移”所有权)。

  • 不支持拷贝语义(实际是移动,但语法是拷贝),容易引发误解和错误。

  • 不支持 std::vector 等标准容器(因为容器要求拷贝安全)。

  • 不支持数组和自定义删除器。

  • 在无 unique_ptr 的年代提供了基本的自动内存管理能力,提供简单 RAII,防止某些情况下的内存泄漏。

  • 已被 unique_ptr 完全取代

4.2 缺点

  • 拷贝语义具有“陷阱”:看似复制,实则转移,代码易出错。
  • 与 STL 容器不兼容(例如插入 auto_ptrvector 会导致运行时崩溃)。
  • 无法支持 nullptr 安全初始化(早期无 nullptr)。
  • 不支持移动语义(现代 C++ 的核心机制),设计落后。
  • 易导致悬空指针或误用。

4.3 使用示例

(请勿在新代码中使用)

// 已弃用,禁止在新代码中使用
std::auto_ptr<MyClass> ptr(new MyClass());
std::auto_ptr<MyClass> ptr2 = ptr; // ptr 自动变为 nullptr,容易出错

替代方案:使用 std::unique_ptr

std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
std::unique_ptr<MyClass> ptr2 = std::move(ptr); // 明确移动,语义清晰
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇