C++ 智能指针(八股总结)

C++中的智能指针有哪些,各自有什么作用?

智能指针主要解决一个内存泄露的问题,它可以自动地释放内存空间。因为它本身是一个类,当函数结束的时候会调用析构函数,并由析构函数释放内存空间。智能指针分为共享指针(shared_ptr), 独占指针(unique_ptr)和弱指针(weak_ptr):

(1)shared_ptr ,多个共享指针可以指向相同的对象,采用了引用计数的机制,当最后一个引用销毁时,释放内存空间;

(2)unique_ptr,保证同一时间段内只有一个智能指针能指向该对象(可通过move操作来传递unique_ptr);

(3)weak_ptr,用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

std::unique_ptr

定义

std::unique_ptr 是独占所有权的智能指针,一个对象只能被一个std::unique_ptr管理,不能共享所有权。

主要用途

  1. 自动释放资源:当std::unique_ptr超出作用域时,会自动释放所管理的资源。
  2. 独占所有权:确保同一资源不会被多个指针操作,避免潜在的内存问题。
  3. 高效传递所有权:通过std::move可以将资源所有权转移。

示例代码

#include <iostream>
#include <memory>

void uniquePtrExample() {
    std::unique_ptr<int> uptr1 = std::make_unique<int>(10); // 创建并管理资源
    std::cout << "Value: " << *uptr1 << std::endl;

    // std::unique_ptr<int> uptr2 = uptr1; // 错误!不能复制
    std::unique_ptr<int> uptr2 = std::move(uptr1); // 转移所有权
    if (!uptr1) {
        std::cout << "uptr1 is now empty." << std::endl;
    }
    std::cout << "Value from uptr2: " << *uptr2 << std::endl;
}

int main() {
    uniquePtrExample();
    return 0;
}

输出

Value: 10
uptr1 is now empty.
Value from uptr2: 10

std::shared_ptr

定义

std::shared_ptr 是具有共享所有权的智能指针,多个std::shared_ptr可以同时管理同一资源,资源会在最后一个std::shared_ptr销毁时释放。

主要用途

  1. 共享资源的生命周期管理:适用于多个对象需要访问和管理同一资源的场景。
  2. 引用计数:内部维护一个引用计数,记录资源被引用的次数。
  3. 线程安全:引用计数是线程安全的。

示例代码

#include <iostream>
#include <memory>

void sharedPtrExample() {
    std::shared_ptr<int> sptr1 = std::make_shared<int>(20); // 创建并管理资源
    std::shared_ptr<int> sptr2 = sptr1; // 共享资源
    std::cout << "Value: " << *sptr1 << ", Use count: " << sptr1.use_count() << std::endl;

    sptr2.reset(); // sptr2释放共享的资源
    std::cout << "After reset, Use count: " << sptr1.use_count() << std::endl;
}

int main() {
    sharedPtrExample();
    return 0;
}

输出

Value: 20, Use count: 2
After reset, Use count: 1

std::weak_ptr

定义

std::weak_ptr 是一种弱引用的智能指针,它不会增加资源的引用计数,通常与std::shared_ptr配合使用,解决循环引用问题。

主要用途

  1. 解决循环引用问题:避免std::shared_ptr之间的循环引用导致资源无法释放。
  2. 观察资源状态:可以安全地观察std::shared_ptr管理的资源是否仍然存在。

示例代码

#include <iostream>
#include <memory>

void weakPtrExample() {
    std::shared_ptr<int> sptr = std::make_shared<int>(30);
    std::weak_ptr<int> wptr = sptr; // 创建弱引用

    std::cout << "Use count: " << sptr.use_count() << std::endl;
    if (auto sp = wptr.lock()) { // 检查资源是否有效
        std::cout << "Value: " << *sp << std::endl;
    }

    sptr.reset(); // 释放资源
    if (wptr.expired()) {
        std::cout << "Resource has been released." << std::endl;
    }
}

int main() {
    weakPtrExample();
    return 0;
}

输出

Use count: 1
Value: 30
Resource has been released.

区别分析
特性std::unique_ptrstd::shared_ptrstd::weak_ptr
所有权独占所有权共享所有权无所有权,仅观察资源
引用计数
典型用途独占资源管理共享资源管理解决循环引用问题
转移操作通过std::move支持复制和共享不直接管理资源

shared_ptr的实现原理是什么?构造函数、拷贝构造函数和赋值运算符怎么写?shared_ptr是不是线程安全的?

(1)shared_ptr是通过引用计数机制实现的,引用计数存储着有几个shared_ptr指向相同的对象,当引用计数下降至0时就会自动销毁这个对象;

(2)具体实现:

1)构造函数:将指针指向该对象,引用计数置为1;

2)拷贝构造函数:将指针指向该对象,引用计数++;

3)赋值运算符:=号左边的shared_ptr的引用计数-1,右边的shared_ptr的引用计数+1,如果左边的引用技术降为0,还要销毁shared_ptr指向对象,释放内存空间。

(3)shared_ptr的引用计数本身是安全且无锁的,但是它指向的对象的读写则不是,因此可以说shared_ptr不是线程安全的。[shared_ptr是线程安全的吗? - 云+社区 - 腾讯云 (tencent.com)](

shared_ptr 的循环引用
定义

shared_ptr 的循环引用(circular reference)是指两个或多个 shared_ptr 实例通过相互引用彼此共享的资源,导致引用计数无法归零,从而无法释放资源的问题。

问题原因
  • shared_ptr 通过引用计数来管理资源。
  • 如果两个 shared_ptr 互相引用,计数器永远不会归零,资源也就无法释放,造成 内存泄漏

示例代码

以下是一个简单的循环引用示例:

#include <iostream>
#include <memory>

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

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();

    // 创建循环引用
    node1->next = node2;
    node2->next = node1;

    // 离开作用域,理论上 node1 和 node2 应该被销毁
    return 0;
}

// 输出:
// (没有任何 "Node destroyed" 的输出)

在上述代码中:

  1. node1node2 各自的 shared_ptr 引用计数为 1。
  2. 它们通过 next 成员指针互相引用,导致 shared_ptr 的引用计数始终为 1。
  3. 离开作用域时,引用计数无法归零,析构函数不会被调用,资源泄漏。

解决方案:使用 weak_ptr

为了避免循环引用,可以使用 weak_ptr 替代其中一个 shared_ptr,因为 weak_ptr 不会增加引用计数。

修正后的代码
#include <iostream>
#include <memory>

class Node {
public:
    std::weak_ptr<Node> next; // 使用 weak_ptr 打破循环
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();

    node1->next = node2;
    node2->next = node1; // 使用 weak_ptr 不会增加引用计数

    return 0;
}

// 输出:
// Node destroyed
// Node destroyed
### C++ 后端开发面试常见问题及答案 #### 编译型与解释型语言的区别 C++ 是一种编译性语言,这意味着源代码会被预先翻译成机器码并生成可执行文件。这种预处理过程使得程序运行速度更快,因为最终执行的是针对特定硬件架构优化后的二进制指令集[^1]。 Python 则属于解释性语言范畴,其特点是通过解释器逐行读取并解析脚本中的命令,在每次执行时都需要重复这个过程。由于缺少提前编译这一步骤,通常情况下 Python 的性能不如 C++ 来得高效。 #### Java 中 `volatile` 关键字的作用 在Java编程里,当声明变量为 volatile 之后,它表示该字段可能会被多个线程同时访问,并且每一次对该字段的读写都会直接作用于主内存而非缓存副本上;此外还提供了可见性和一定程度上的原子性保障,确保不同步条件下也能维持数据的一致状态[^2]。 #### 内存管理之动态分配差异对比 (`new` vs `malloc`) 对于 C++ 而言,“new”不仅负责创建对象实例同时还调用了相应的构造方法完成初始化工作,返回指向新建立实体的具体类型指针;而标准库函数 malloc() 只单纯地请求一定数量连续字节的空间作为缓冲区使用,所得到的结果是一个通用性的 void * 形式的地址值,需手动转换为目标结构体或基本数值类型的指示符以便后续操作[^3]。 #### 栈与堆之间的效能考量 栈区域内的资源管理和释放机制相对简单明了——遵循先进后出原则(LIFO),并且拥有专门设计好的 CPU 寄存器支持以及高效的入栈/退栈动作实现方式,故而在大多数场景下能够提供优于堆的表现效果。相反地,堆则依赖于应用程序层面提供的 API 完成交互流程,涉及到更为复杂的逻辑判断环节(比如寻找适当大小区块),再加上间接寻址带来的额外开销,整体响应时间自然也就延长了许多[^4]。 #### 特殊成员函数修饰符的意义 (=default 和 =delete) 为了增强类定义灵活性的同时保持语义清晰度,现代 C++ 引入了两种特殊的语法糖形式用于控制某些内置行为的发生与否:`= default` 显式告知编译工具按照默认模式合成指定的方法签名版本;`= delete` 则明确禁止此类接口的存在可能性,从而防止潜在错误发生或是出于安全考虑限制外部不可控因素的影响范围[^5]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值