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

C/C++ 面试智能指针

说下你对智能指针的理解

回答1:

因为C++使用内存的时候很容易出现野指针、悬空指针、内存泄露的问题。所以C++11引入了智能指针来管理内存。有四种:

  • auto_ptr:已经不用了
  • unique_ptr:独占式指针,同一时刻只能有一个指针指向同一个对象
  • shared_ptr:共享式指针,同一时刻可以有多个指针指向同一个对象
  • weak_ptr:用来解决shared_ptr相互引用导致的死锁问题

回答2:

1.std::unique_ptr

所有权模型:独占所有权,同一时间只能有一个unique_ptr指向资源。

核心特点

  • 不可复制(delete了拷贝构造函数和拷贝赋值),但支持移动语义(std::move)。

  • 轻量级,性能接近裸指针。

使用场景

管理独占资源(如文件句柄、动态数组)。

替代new/delete,作为工厂函数的返回值

std::unique_ptr<int> p1 = std::make_unique<int>(42);
std::unique_ptr<int> p2 = std::move(p1); // 所有权转移
2.std::shared_ptr

所有权模型:共享所有权,通过引用计数管理资源。

核心特点
  • 多个shared_ptr可以指向同一资源,引用计数为0时释放资源。

  • 支持自定义删除器(如释放文件、网络连接等非内存资源)。
    潜在问题

潜在问题
  • 循环引用:若两个shared_ptr相互引用,引用计数无法归零,导致内存泄漏。
解决方案
auto p1 = std::make_shared<int>(42);
auto p2 = p1; // 引用计数+1
3.std::weak_ptr

所有权模型:弱引用,不增加引用计数。

核心作用:
  • 解决 shared_ptr 的循环引用问题。

  • 临时观察资源是否存在(通过lock()升级为shared_ptr)。

示例:
std::weak_ptr<int> wp = p1;
if (auto sp = wp.lock()) { // 若资源存在,升级为shared_ptr
    // 使用sp访问资源
}
智能指针的实现原理

unique_ptr

  • 封装裸指针,通过删除移动拷贝操作确保独占所有权。

shared_ptr

  • 包含两个指针:一个指向资源,一个指向控制块(含引用计数和删除器)。

  • 引用计数通过原子操作保证线程安全。

weak_ptr

  • 同样指向控制块,但不参与引用计数。
使用智能指针的最佳实践
优先使用make_shared和make_unique
auto p = std::make_shared<Object>(args); // 推荐
  • 避免显式new,提高异常安全性。

  • make_shared会合并资源与控制块的内存分配,提升性能。

避免混用裸指针和智能指针
int* raw_ptr = p.get(); // 谨慎使用get(),避免delete或延长生命周期。
自定义删除器
std::shared_ptr<FILE> file( fopen("test.txt", "r"), [](FILE* f) { fclose(f); } );

unique_ptr 能否被另一个 unique_ptr 拷贝呢

回答1:

不能,因为它把它的拷贝构造函数private了。但是它提供了一个移动构造函数,所以可以通过std::move将指针指向的对象交给另一个unique_ptr,转交之后自己就失去了这个指针对象的所有权,除非被显示交回

回答2:

为什么不能拷贝?
1.独占所有权:
  • std::unique_ptr 的核心设计是独占资源的所有权。如果允许拷贝,会导致多个指针指向同一资源,违背了独占所有权的原则。

  • 如果允许拷贝,资源释放时会出现重复释放的问题,导致未定义行为。

2.禁止拷贝构造函数和拷贝赋值运算符:
  • std::unique_ptr 的拷贝构造函数和拷贝赋值运算符被显式删除(= delete),因此无法直接拷贝。
std::unique_ptr<int> p1 = std::make_unique<int>(42);
std::unique_ptr<int> p2 = p1; // 错误:拷贝构造函数被删除
std::unique_ptr<int> p3;
p3 = p1; // 错误:拷贝赋值运算符被删除
2.如何转移所有权?

虽然 std::unique_ptr 不能拷贝,但可以通过移动语义(std::move)将资源的所有权从一个 std::unique_ptr 转移到另一个。

std::unique_ptr<int> p1 = std::make_unique<int>(42); // p1 拥有资源
std::unique_ptr<int> p2 = std::move(p1); // 将资源从 p1 转移到 p2

if (p1 == nullptr) {
    std::cout << "p1 is now null, ownership transferred to p2." << std::endl;
}
std::move 的作用:

将 p1 的资源所有权转移到 p2。

转移后,p1 变为 nullptr,不再拥有资源。

总结
  • std::unique_ptr 不能被拷贝,因为它遵循独占所有权的语义。

  • 可以通过 std::move 转移所有权,转移后原指针变为 nullptr。

  • 这种设计确保了资源管理的安全性和明确性,避免了内存泄漏和重复释放的问题。

unique_ptr和shared_ptr的区别

unique_ptr

unique_ptr 是独占所有权的智能指针。它确保一个资源在任何时候只有一个 unique_ptr 拥有它。这意味着,unique_ptr 不允许多个指针指向同一个资源,资源的所有权不能被共享。

特点

  • 独占所有权:一个 unique_ptr 拥有资源的唯一所有权,不能被拷贝。只能通过 std::move() 转移所有权。
  • 内存自动释放:当 unique_ptr 被销毁时,它所管理的资源会自动释放。
  • 不可拷贝,但可以转移:unique_ptr 不能被拷贝,避免了资源的多重释放问题,但是可以通过 std::move() 转移所有权。
#include <iostream>
#include <memory>

void uniquePtrExample() {
    // 创建 unique_ptr 并指向一个动态分配的整数
    std::unique_ptr<int> p1 = std::make_unique<int>(10);
    std::cout << "Value of p1: " << *p1 << std::endl;

    // 转移所有权
    std::unique_ptr<int> p2 = std::move(p1);

    // p1 现在是空指针
    if (!p1) {
        std::cout << "p1 is empty after move." << std::endl;
    }

    // p2 拥有资源
    std::cout << "Value of p2: " << *p2 << std::endl;
}

shared_ptr

shared_ptr 是共享所有权的智能指针。多个 shared_ptr 实例可以共享对同一资源的所有权。每当一个 shared_ptr 被拷贝时,资源的引用计数会增加。只有当所有指向该资源的 shared_ptr 都被销毁时,资源才会被释放。

特点
  • 共享所有权:多个 shared_ptr 可以共享同一个资源的所有权,资源的引用计数由 shared_ptr 内部自动管理。
  • 引用计数:每次拷贝一个 shared_ptr,引用计数增加,销毁一个 shared_ptr,引用计数减少。资源会在引用计数变为零时释放。
  • 线程安全:shared_ptr 是线程安全的,多个线程可以安全地使用同一个 shared_ptr。
#include <iostream>
#include <memory>

void sharedPtrExample() {
    // 创建 shared_ptr 并指向一个动态分配的整数
    std::shared_ptr<int> p1 = std::make_shared<int>(20);
    std::cout << "Value of p1: " << *p1 << std::endl;

    // 创建另一个 shared_ptr 并共享资源
    std::shared_ptr<int> p2 = p1;
    std::cout << "Value of p2: " << *p2 << std::endl;

    // 引用计数为 2
    std::cout << "Reference count: " << p1.use_count() << std::endl;

    // p1 和 p2 都会在其生命周期结束时释放资源
}


主要区别总结
特性unique_ptrshared_ptr
所有权独占所有权,不能共享共享所有权,多个指针可以指向同一资源
拷贝不允许拷贝,只能转移所有权 (std::move)支持拷贝,引用计数增加
内存管理资源的释放由 unique_ptr 自动进行引用计数机制,所有 shared_ptr 被销毁时自动释放资源
性能性能较高,因为不涉及引用计数相较于 unique_ptr,性能较低,因为需要维护引用计数
线程安全非线程安全(不同线程中的 unique_ptr 不可共享)线程安全(引用计数是线程安全的,但资源访问需要加锁)
选择使用的场景
  • 使用 unique_ptr:适用于资源的唯一拥有者场景,能够提供高效、简洁的内存管理,不允许共享所有权。
    • 如:资源管理、文件句柄、网络连接等。
  • 使用 shared_ptr:适用于多个对象需要共享资源所有权的场景,例如资源共享、动态配置、对象池等。
    • 如:图形界面的窗口管理、图形对象共享、数据库连接池等。

总结

  • unique_ptr 是用来实现独占式所有权的智能指针,不能拷贝,可以转移所有权,适用于资源的唯一拥有者场景。
  • shared_ptr 是用来实现共享所有权的智能指针,可以被多个 shared_ptr 对象共享,并通过引用计数管理资源的生命周期,适用于多个对象共享资源的场景。

unique_ptr = unique_ptr 和 shared_ptr=shared_ptr这两个操作有什么后果呢?

回答1:

unique_ptr = unique_ptr :

unique_ptr 是独占所有权的智能指针,它不允许拷贝或赋值操作。直接执行 unique_ptr = unique_ptr 会导致编译错误。unique_ptr 的设计目的是确保资源只能有一个所有者,避免了资源的多重释放问题。

后果:

编译错误:因为 unique_ptr 不允许拷贝和赋值,只能通过 std::move() 转移所有权。假如你尝试执行 unique_ptr = unique_ptr,编译器会报错,提示无法拷贝 unique_ptr 对象。

  unique_ptr<int> p1 = make_unique<int>(10);
  unique_ptr<int> p2 = make_unique<int>(20);
 
  // 编译错误:unique_ptr 不允许拷贝或赋值
  p1 = p2;  // Error: unique_ptr不能直接赋值
解决方法:

unique_ptr 只能通过 std::move() 进行所有权转移。例如:

p1 = std::move(p2);  // p1 接管 p2 所有权,p2 被置为空
解释:

std::move(p2) 会把 p2 的所有权转移给 p1,此时 p2 成为一个空指针,不能再访问原来的资源。

shared_ptr = shared_ptr:

shared_ptr 是共享所有权的智能指针,允许多个 shared_ptr 对象共享同一个资源。在执行 shared_ptr = shared_ptr 操作时,引用计数会增加,即新的 shared_ptr 会共享原来指针的资源。

后果

正确: shared_ptr 允许多个 shared_ptr 指向同一个资源,通过引用计数管理资源的生命周期。当你将一个 shared_ptr 赋值给另一个时,引用计数会增加,表示更多的指针共享该资源。当所有指向该资源的 shared_ptr 被销毁时,资源才会被释放。

   shared_ptr<int> p1 = make_shared<int>(10);
    cout << "Reference count of p1: " << p1.use_count() << endl;  // 输出: 1

    shared_ptr<int> p2 = p1;  // 共享所有权,p1 和 p2 都指向同一个资源
    cout << "Reference count of p2: " << p2.use_count() << endl;  // 输出: 2
解释

当 p2 = p1 时,p1 和 p2 都指向同一个资源。此时,引用计数从 1 增加到 2,表示有两个 shared_ptr 对象共享资源。当这两个 shared_ptr 都被销毁时,资源才会被释放。

总结对比
  • unique_ptr = unique_ptr:编译错误,因为 unique_ptr 是独占所有权的,不允许拷贝或赋值。
    • 正确操作:通过 std::move() 转移所有权。
  • shared_ptr = shared_ptr:合法操作,引用计数增加,多个 shared_ptr 可以共享同一个资源,资源会在最后一个 shared_ptr 被销毁时释放。
实际应用
  • unique_ptr:适用于需要确保资源有唯一所有者的场景,避免了多重释放资源的问题。
  • shared_ptr:适用于多个对象共享资源的场景,自动管理资源的引用计数,确保资源在所有者全部销毁后才被释放。

shared_ptr的移动赋值时发生了什么事情

  • 首先它会检查本指针和参数指针是不是同一个对象,如果是,直接返回
  • 然后,先把本指针的引用变量–,如果发现减到了0,就把参数指针和参数引用变量析构掉并置NULL
  • 最后,本指针和参数指针指向同一个对象以及引用计数,然后引用计数自增1

shared_ptr什么时候会被释放掉,就是什么时候引用次数为0?

  • 在一个shared_ptr析构时,或者被赋值时,强引用计数会减1。减到0就delete资源

shared_ptr 你是怎么实现的?

shared_ptr 的核心实现基于 引用计数(Reference Counting)。它的基本原理是:

  1. 每个资源 由一个 shared_ptr 所管理,同时维护一个 引用计数(use_count)。
  2. 当一个新的 shared_ptr 复制已有的 shared_ptr ,引用计数增加。
  3. 当一个 shared_ptr 被销毁时,引用计数减少。
  4. 当引用计数归零时,释放所管理的资源。

自己实现一个 shared_ptr

简化版的 shared_ptr,主要包含:

  • 资源指针 (T* ptr)
  • 引用计数 (int* count)
  • 拷贝构造 & 赋值操作(增加引用计数)
  • 析构函数(减少引用计数,归零时释放资源)
#include <iostream>

template <typename T>
class MySharedPtr {
private:
    T* ptr;       // 资源指针
    int* count;   // 引用计数

public:
    // **构造函数**
    explicit MySharedPtr(T* p = nullptr) : ptr(p) {
    // ptr 指向新建资源
    // count 记录资源的引用次数
        if (ptr) {
            count = new int(1);  // 初始化引用计数为 1
        } else {
            count = new int(0);
        }
    }

    // **拷贝构造函数(增加引用计数)**  -> 复制 ptr 和 count,即共享资源
    MySharedPtr(const MySharedPtr& other) : ptr(other.ptr), count(other.count) {
        if (ptr) {
            (*count)++;  // 增加引用计数
        }
    }

    // **赋值运算符(处理自赋值 & 管理引用计数)**
    MySharedPtr& operator=(const MySharedPtr& other) {
        if (this == &other)  // 避免自赋值
            return *this;

        // 先释放当前对象的资源
        release();

        // 复制新对象
        ptr = other.ptr;
        count = other.count;
        if (ptr) {
            (*count)++;  // 增加引用计数
        }

        return *this;
    }

    // **释放资源(减少引用计数)**
    void release() {
        if (ptr && --(*count) == 0) { // 引用计数归零时释放资源
            delete ptr;
            delete count;
        }
    }

    // **析构函数**
    ~MySharedPtr() {
        release();
    }

    // **获取原始指针**
    T* get() const { return ptr; }

    // **重载 `->` 和 `*` 以便像指针一样使用**
    T* operator->() const { return ptr; }
    T& operator*() const { return *ptr; }

    // **获取引用计数**
    int use_count() const { return *count; }
};

// **测试 `MySharedPtr`**
int main() {
    MySharedPtr<int> p1(new int(42));
    std::cout << "p1 count: " << p1.use_count() << std::endl; // 1

    MySharedPtr<int> p2 = p1; // 复制 p1
    std::cout << "p1 count after copy: " << p1.use_count() << std::endl; // 2
    std::cout << "p2 count: " << p2.use_count() << std::endl; // 2

    {
        MySharedPtr<int> p3 = p2; // 复制 p2
        std::cout << "p3 count: " << p3.use_count() << std::endl; // 3
    } // p3 作用域结束,引用计数减少

    std::cout << "p1 count after p3 destroyed: " << p1.use_count() << std::endl; // 2

    return 0;
}

shared_ptr是不是线程安全?

结论

std::shared_ptr 本身是线程安全的,但它管理的对象 不是 线程安全的。

线程安全的部分
1. 引用计数的增加/减少是原子操作
  • std::shared_ptr 内部使用了 原子操作 来管理引用计数(std::atomic 或等效实现)。
  • 这意味着多个线程可以同时 拷贝销毁 shared_ptr,不会导致未定义行为。
  • 例如,多个线程可以同时调用 std::shared_ptr p1 = p2; 或 p1.reset();,不会导致引用计数错误。
线程不安全的部分
1.共享的对象本身不是线程安全的
  • shared_ptr 只是管理了对象的生命周期,但不提供对象访问的同步保护。
  • 如果多个线程同时 读取/修改 shared_ptr 所管理的对象,而没有额外的同步机制(如 std::mutex),会导致 数据竞争 和 未定义行为。
std::shared_ptr<int> p = std::make_shared<int>(42);
std::thread t1([&]() { *p = 10; });  // 修改值
std::thread t2([&]() { std::cout << *p << std::endl; });  // 读取值
t1.join();
t2.join();

上面的代码 可能会崩溃 或 输出未定义结果,因为 *p 被多个线程同时访问,没有同步保护。

2.不同 shared_ptr 实例对同一对象的修改是线程不安全的

例如:

std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1;  // 线程安全(增加引用计数)
std::thread t1([&]() { p1.reset(); });  // 线程1释放对象
std::thread t2([&]() { p2.reset(); });  // 线程2释放对象

这虽然不会导致引用计数错误,但如果 reset() 操作后仍然访问 p1 或 p2,可能导致 访问已释放的对象(悬垂指针)。

如何保证线程安全?
  1. 只在单个线程中访问 shared_ptr 管理的对象
  2. 使用 std::mutex 保护对象访问
std::mutex mtx;
std::shared_ptr<int> p = std::make_shared<int>(42);

std::thread t1([&]() {
    std::lock_guard<std::mutex> lock(mtx);
    *p = 10;
});

std::thread t2([&]() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << *p << std::endl;
});

t1.join();
t2.join();

3.使用 std::atomic<std::shared_ptr>

std::atomic<std::shared_ptr> 允许线程安全地 更新 shared_ptr(但仍不保证管理的对象是线程安全的)。

std::atomic<std::shared_ptr<int>> atomic_p(std::make_shared<int>(42));
std::thread t1([&]() { atomic_p.store(std::make_shared<int>(100)); });
std::thread t2([&]() { std::cout << *atomic_p.load() << std::endl; });
t1.join();
t2.join();

这样可以保证 shared_ptr 本身的更新是安全的,但仍然需要同步对象访问。

总结

线程安全性shared_ptr
拷贝/赋值/销毁√ 线程安全(原子操作)
管理的对象访问× 线程不安全(需要同步)
多个 shared_ptr 指向同一对象√ 线程安全(引用计数安全)
多个线程同时修改 shared_ptr× 线程不安全(需 std::mutex 或 std::atomic)

如果你在多线程环境下使用 shared_ptr,确保:

  • 线程不会同时修改管理的对象,或者使用 std::mutex 保护访问。
  • 需要线程安全的 shared_ptr 赋值时,可以使用 std::atomic<std::shared_ptr>。

sharedPtr在64位操作系统下大小有多大

  • 两个指针,64字节

shared_ptr和weak_ptr之间是什么关系?

weak_ptr是用来辅助shared_ptr的,每一个weak_ptr它指向weak_ptr而不是实际的操作函数。
std::weak_ptr 是 std::shared_ptr 的 弱引用,它和 shared_ptr 之间有以下关系

1. weak_ptr 解决 shared_ptr 的循环引用问题

问题:shared_ptr 可能导致循环引用,导致内存泄漏
当两个 shared_ptr 相互引用时,它们的引用计数不会降为 0,导致对象无法释放。例如:

#include <iostream>
#include <memory>

class B;  // 前置声明

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

class B {
public:
    std::shared_ptr<A> a_ptr;
    ~B() { std::cout << "B 被销毁\n"; }
};

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

    a->b_ptr = b;
    b->a_ptr = a;  // 循环引用

    return 0;  // 内存泄漏:A 和 B 都不会被销毁
}

解决方案:用 weak_ptr 代替 shared_ptr 解决循环引用
weak_ptr 不会增加引用计数,所以 shared_ptr 之间不会出现循环依赖:

class B;  // 前置声明

class A {
public:
    std::weak_ptr<B> b_ptr;  // 改为 weak_ptr
    ~A() { std::cout << "A 被销毁\n"; }
};

class B {
public:
    std::shared_ptr<A> a_ptr;
    ~B() { std::cout << "B 被销毁\n"; }
};

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

    a->b_ptr = b;  // 现在是 weak_ptr
    b->a_ptr = a;

    return 0;  // A 和 B 都能正常释放
}

2. weak_ptr 不影响 shared_ptr 的引用计数

  • std::weak_ptr 不会增加引用计数(use_count() 不变)
  • std::weak_ptr 只持有对象的弱引用,如果对象已经被释放,weak_ptr 变成 空指针
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp = std::make_shared<int>(42);
    std::weak_ptr<int> wp = sp;  // 不增加引用计数

    std::cout << "use_count: " << sp.use_count() << std::endl;  // 输出 1

    sp.reset();  // 释放 shared_ptr

    if (wp.expired()) {
        std::cout << "对象已被销毁\n";  // wp 变为空指针
    }

    return 0;
}

3. weak_ptr 需要 lock() 访问对象

weak_ptr 不能直接访问对象,要用 lock() 获取 shared_ptr,然后再使用:

std::weak_ptr<int> wp;

{
    std::shared_ptr<int> sp = std::make_shared<int>(42);
    wp = sp;  // 赋值,不增加引用计数
}

if (std::shared_ptr<int> sp = wp.lock()) {  // 获取 shared_ptr
    std::cout << *sp << std::endl;
} else {
    std::cout << "对象已被销毁\n";
}

为什么推荐用makeshared创建指针?

1. 避免额外的内存分配,提高性能

使用 new 创建 shared_ptr,需要 两次内存分配

  1. 分配对象的内存 (new T)
  2. 分配 shared_ptr 的控制块(用于存储引用计数)

但 std::make_shared 只分配一次内存,控制块和对象存在同一块内存区域,减少了额外的分配和释放操作,提高性能。

std::shared_ptr<int> sp1(new int(42)); // 两次分配(对象 + 控制块)
std::shared_ptr<int> sp2 = std::make_shared<int>(42); // 一次分配(对象+控制块一起分配)

结论: make_shared 比 new + shared_ptr 方式更快、更高效。

2. 避免异常安全问题

如果 new 之后发生异常,可能导致 内存泄漏。

错误示例(可能泄漏内存):
std::shared_ptr<int> sp1(new int(42));  // new 成功后,sp1 可能抛出异常
  • new int(42) 先执行
  • std::shared_ptr 的构造函数可能抛异常(比如 bad_alloc)
  • new 分配的内存不会被释放,造成泄漏
正确做法(异常安全):
std::shared_ptr<int> sp1 = std::make_shared<int>(42);

原因: make_shared 直接分配对象并初始化 shared_ptr,不会导致内存泄漏。

3. 减少 shared_ptr 的构造开销

使用 new 方式:

auto sp1 = std::shared_ptr<int>(new int(42));  // 两步:new + shared_ptr 构造

使用 make_shared 方式:

auto sp2 = std::make_shared<int>(42);  // 直接分配和初始化

make_shared 更高效,代码更简洁

4. make_shared 允许数组初始化

C++17 之后,make_shared 支持数组:

auto sp = std::make_shared<int[]>(10);  // 分配 10 个 int

结论:

推荐用 std::make_shared,因为它:
✅ 性能更高(减少一次内存分配)
✅ 避免异常安全问题(不会导致内存泄漏)
✅ 代码更简洁(不需要 new)
✅ 减少 shared_ptr 构造开销

⚠️ 但如果 shared_ptr 需要自定义删除器,则必须手动 new,不能用 make_shared。

为什么要用 shared_from_this?

什么是 shared_from_this

shared_from_this 是 std::enable_shared_from_this 提供的一个方法,允许 一个类的成员函数安全地获取自身的 shared_ptr。

为什么要用 shared_from_this?

如果一个对象已经被 std::shared_ptr 管理,直接用 this 构造新的 shared_ptr 会导致严重错误(多重 shared_ptr 造成对象被提前释放)。

错误示例(手动构造 shared_ptr 导致对象重复释放):
class MyClass {
public:
    void DangerousMethod() {
        // ❌ 直接创建 shared_ptr,会导致双重管理
        std::shared_ptr<MyClass> sp_this(this);
    }
};

int main() {
    std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>();
    sp1->DangerousMethod(); // ⚠️ sp1 和 sp_this 现在都在管理同一个对象
    return 0;
}  // ❌ 可能导致对象被删除两次(double free)

问题:

  • sp1 和 sp_this 都认为自己是 唯一的 shared_ptr,它们会各自删除对象,导致 double free(重复释放) 或 段错误。
解决方案:使用 shared_from_this
#include <iostream>
#include <memory>

class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    std::shared_ptr<MyClass> SafeMethod() {
        return shared_from_this(); // ✅ 返回正确的 shared_ptr
    }
};

int main() {
    std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>();
    std::shared_ptr<MyClass> sp2 = sp1->SafeMethod(); // ✅ 共享同一个控制块
    std::cout << "sp1.use_count() = " << sp1.use_count() << std::endl; // 输出 2
    return 0;
}

为什么 shared_from_this() 安全?

  • shared_from_this() 不会创建新的控制块,而是返回现有的 shared_ptr,保证同一个对象只有一个引用计数管理。

shared_from_this 适用场景

  1. 对象需要在方法内部创建 shared_ptr,但不能改变已有的 shared_ptr 计数
  2. 观察者模式(Observer Pattern)
    • 监听者持有 shared_ptr,如果通知时直接用 this 可能导致悬挂指针
  3. 回调(Callback)
    • 避免 std::bind 或 std::function 误用 this,导致悬挂指针
  4. 多线程任务调度
  • 任务可能在 shared_ptr 作用域外完成,确保对象存活

总结

✅ shared_from_this() 确保 shared_ptr 共享相同的控制块,避免重复管理对象
✅ 防止 this 直接构造 shared_ptr 造成的内存错误
✅ 适用于回调、观察者模式、多线程等场景
⚠️ 使用 shared_from_this 的类必须继承 std::enable_shared_from_this

weak_ptr的原理

std::weak_ptr 是 std::shared_ptr 的辅助指针,它不会影响引用计数,主要用于 解决循环引用问题 和 临时访问 shared_ptr 管理的对象。

weak_ptr 的底层原理
  1. weak_ptr 和 shared_ptr 共享同一个控制块(Control Block)
    • shared_ptr 内部有 引用计数(strong count) 和 弱引用计数(weak count)。
    • weak_ptr 仅增加 弱引用计数,不会影响 强引用计数。
  2. weak_ptr 不能直接使用对象,需要 lock() 转换为 shared_ptr
    • weak_ptr 不能直接访问资源,因为可能已经被释放。
    • 通过 lock() 方法获得 shared_ptr,如果对象已销毁,lock() 返回 nullptr。
底层数据结构(简化示意图)
struct ControlBlock {
    int strong_count;  // 强引用计数(shared_ptr 持有)
    int weak_count;    // 弱引用计数(weak_ptr 持有)
    void* ptr;         // 指向对象的指针
};

总结

✅ weak_ptr 不增加引用计数,避免循环引用问题
✅ lock() 方法用于安全访问对象,防止 dangling pointer(悬挂指针)
✅ 适用于缓存、观察者模式、循环引用等场景
⚠️ weak_ptr 不能直接使用,需要 lock() 转换成 shared_ptr

weak_ptr的使用场景

1. weak_ptr 解决循环引用

#include <iostream>
#include <memory>

class B; // 前向声明

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

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

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->ptrB = b;
    b->ptrA = a; // ✅ 这里使用 weak_ptr,否则 A 和 B 互相引用,永远不会释放

    return 0; // ✅ A 和 B 都会被正确销毁
}

如果 ptrA 也是 shared_ptr,会发生内存泄漏

class B {
public:
    std::shared_ptr<A> ptrA; // ❌ 这样会导致循环引用,内存永远无法释放
};

2. lock() 获取 shared_ptr

#include <iostream>
#include <memory>

class MyClass {
public:
    void show() { std::cout << "Object is alive!\n"; }
};

int main() {
    std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();
    std::weak_ptr<MyClass> wp = sp; // ✅ 创建 weak_ptr

    if (auto shared = wp.lock()) { // ✅ 转换成 shared_ptr
        shared->show();
    } else {
        std::cout << "Object already deleted\n";
    }

    sp.reset(); // ❌ 释放对象

    if (auto shared = wp.lock()) {
        shared->show();
    } else {
        std::cout << "Object already deleted\n"; // ✅ 这里会输出
    }
}

weak_ptr 适用场景

  1. 解决 shared_ptr 循环引用
    • 互相引用的对象,需要一个使用 weak_ptr 避免内存泄漏。
  2. 缓存(Cache)
    • 避免缓存对象始终存活,但仍然可以被共享使用。
  3. 观察者模式(Observer Pattern)
    • 监听者使用 weak_ptr 避免对象一直存活。

关于shared_ptr相互引用具体讲一下

weak_ptr 是为了解决 shared_ptr 双向引用的问题。即:

class B;
struct A{
    shared_ptr<B> b;
};
struct B{
    shared_ptr<A> a;
};
auto pa = make_shared<A>();
auto pb = make_shared<B>();
pa->b = pb;
pb->a = pa;

pa 和 pb 存在着循环引用,根据 shared_ptr 引用计数的原理,pa 和 pb 都无法被正常的释放。
对于这种情况, 我们可以使用 weak_ptr:

class B;
struct A{
    shared_ptr<B> b;
};
struct B{
    weak_ptr<A> a;
};
auto pa = make_shared<A>();
auto pb = make_shared<B>();
pa->b = pb;
pb->a = pa;

weak_ptr 不会增加引用计数,因此可以打破 shared_ptr 的循环引用。
通常做法是 parent 类持有 child 的 shared_ptr, child 持有指向 parent 的 weak_ptr。这样也更符合语义。

weak_ptr如何检测指针是否被销毁

std::weak_ptr 可以通过 expired() 或 lock() 方法来检测管理的对象是否已经被销毁。

方法1:expired()

  • 如果对象已经被销毁,返回 true。
  • 如果对象仍然存活,返回 false。
#include <iostream>
#include <memory>

class MyClass {
public:
    void show() { std::cout << "Object is alive!\n"; }
};

int main() {
    std::weak_ptr<MyClass> wp; // 创建 weak_ptr

    {
        std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(); // 创建 shared_ptr
        wp = sp; // weak_ptr 观察 shared_ptr

        std::cout << "Object still exists: " << std::boolalpha << !wp.expired() << "\n";
    } // `sp` 离开作用域,对象被销毁

    std::cout << "Object still exists: " << std::boolalpha << !wp.expired() << "\n"; // ✅ 这里会输出 false

    return 0;
}

输出:

Object still exists: true
Object still exists: false

方法 2:lock()

  • lock() 返回一个新的 shared_ptr:
    • 如果对象仍然存活,返回有效的 shared_ptr。
    • 如果对象已经销毁,返回空的 shared_ptr(nullptr)。
#include <iostream>
#include <memory>

class MyClass {
public:
    void show() { std::cout << "Object is alive!\n"; }
};

int main() {
    std::weak_ptr<MyClass> wp; // 创建 weak_ptr

    {
        std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(); // 创建 shared_ptr
        wp = sp; // weak_ptr 观察 shared_ptr

        if (auto sp2 = wp.lock()) { // 尝试获取 shared_ptr
            sp2->show();
        } else {
            std::cout << "Object already deleted\n";
        }
    } // `sp` 离开作用域,对象被销毁

    if (auto sp2 = wp.lock()) {
        sp2->show();
    } else {
        std::cout << "Object already deleted\n"; // ✅ 这里会输出
    }

    return 0;
}

输出:

Object is alive!
Object already deleted

结论

方法用途适用场景
expired()检查对象是否销毁只想知道对象是否还活着,不需要访问对象
lock()获取 shared_ptr 访问对象需要访问对象,并避免悬挂指针

建议:

只检查对象是否存活时,使用 expired()。
需要访问对象时,使用 lock(),防止访问已销毁对象导致未定义行为

如何将weak_ptr转换为shared_ptr

在 C++ 中,可以使用 weak_ptr.lock() 方法将 weak_ptr 转换为 shared_ptr。

  • 如果对象仍然存在,lock() 返回一个有效的 shared_ptr,对象的引用计数会增加。
  • 如果对象已被销毁,lock() 返回一个空的 shared_ptr(nullptr)。
#include <iostream>
#include <memory>

class MyClass {
public:
    void show() { std::cout << "Object is alive!\n"; }
};

int main() {
    std::weak_ptr<MyClass> wp;  // 创建 weak_ptr

    {
        std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(); // 创建 shared_ptr
        wp = sp; // weak_ptr 观察 shared_ptr

        // 使用 lock() 转换为 shared_ptr
        std::shared_ptr<MyClass> sp2 = wp.lock();
        if (sp2) {
            sp2->show();
        } else {
            std::cout << "Object already deleted\n";
        }
    } // sp 离开作用域,对象被销毁

    // 再次尝试转换
    std::shared_ptr<MyClass> sp3 = wp.lock();
    if (sp3) {
        sp3->show();
    } else {
        std::cout << "Object already deleted\n";  // ✅ 这里会输出
    }

    return 0;
}

Object is alive!
Object already deleted

lock() 的作用

1. 防止悬挂指针:
  • weak_ptr 本身不能直接访问对象,但 lock() 可以安全地检查对象是否存在,并返回 shared_ptr。
2. 增加引用计数:
  • lock() 成功获取 shared_ptr 后,对象的引用计数会增加,确保对象在作用域内不会被释放。

注意

不要直接使用 expired() + lock(),这种做法可能会引发竞态条件:

if (!wp.expired()) {
    std::shared_ptr<MyClass> sp = wp.lock();  // 可能 wp 在这里已经失效
}

正确做法:直接用 lock()

if (auto sp = wp.lock()) { 
    // 只有 sp 有效时才访问对象
    sp->show();
}

总结

  • 使用 lock() 来转换 weak_ptr 为 shared_ptr,确保对象存在。
  • 避免 expired() + lock(),因为会有竞态条件。
  • 适用于缓存、观察者模式等场景,防止悬挂指针。

http://www.kler.cn/a/536395.html

相关文章:

  • 单硬盘槽笔记本更换硬盘
  • LeetCode-197. 上升的温度
  • 紧跟潮流,将 DeepSeek 集成到 VSCode
  • 【LeetCode】152、乘积最大子数组
  • 车载以太网__传输层
  • k8m 是一款轻量级、跨平台的 Kubernetes 仪表板
  • C++ 中的环形线性动态规划
  • 攻防世界baigeiRSA
  • 【补充】RustDesk一键部署及账号登录配置
  • 深入理解Python上下文管理器:从基础到高级应用
  • java版本
  • 8.stack和queue
  • Linux交叉编译gpsd移植至arm板
  • CI/CD相关概念
  • AWS 上的 Red Hat OpenShift 服务
  • uniapp 使用 tree.js 解决模型加载不出来的问题
  • Python办公笔记——将csv文件转Json
  • c#对接deepseek 聊天AI接口
  • 使用数学工具和大模型结合训练专有小模型(有限元算法和大模型微调)
  • 使用 Docker 部署 RabbitMQ 的详细指南
  • 紧跟潮流,将 DeepSeek 集成到 VSCode
  • Windows 电脑安装 mysqldump 的详细教程
  • 数据结构与算法面经
  • ZooKeeper相关知识点
  • C++ Primer 递增和递减运算符
  • 配置#include “nlohmann/json.hpp“,用于处理json文件