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

server/2025/1/24 0:40:21/

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

http://www.ppmy.cn/server/160889.html

相关文章

服务器安装ESXI7.0系统及通过离线包方式升级到ESXI8.0

新到了一台物理服务器需要安装系统&#xff0c;项目不急用&#xff0c;先拿来做些实验。 本次实验目标&#xff1a; 1、在物理服务器上安装ESXI7.0系统&#xff1b; 2、通过离线包升级方式将ESXI7.0升级为ESXI8.0。 实验环境准备&#xff1a; 物理服务器1台&#xff0c;型号…

【C++】在线五子棋对战项目网页版

目录 1.Websocket 1.1.Websocket的简单认识 1.2.什么是轮询呢&#xff1f; 1.3.websocket协议切换过程 1.4.websocketpp库常用接口认识 1.5.websocketpp库搭建服务器流程 1.6.websocketpp库搭建服务器 2.mysqlclient库-接口认识 3.项目模块的划分&#xff1a; 4.项目…

“推理”(Inference)在深度学习和机器学习的语境

“推理”&#xff08;Inference&#xff09;在深度学习和机器学习的语境中&#xff0c;是指使用经过训练的模型对新数据进行预测的过程。将其简单地理解为“模型的应用阶段”。在这一阶段&#xff0c;我们不再进行模型训练&#xff0c;而是利用已训练好且保存下来的模型来获取对…

react上增加错误边界 当存在错误时 不会显示白屏

react上增加错误边界 当存在错误时 不会显示白屏 定义&#xff1a;错误边界是一个 React 组件&#xff0c;它会在其子组件树中的任何位置捕获 JavaScript 错误&#xff0c;并显示一个备用的 UI 而不是崩溃的组件树 在总项目的组件中创建文件&#xff1a; src/components/Erro…

adb常用指令(完整版)

1、adb devices 查看是否连接到设备 2、adb install [-r] [-s] 安装app&#xff0c;-r强制&#xff0c;-s安装sd卡上 3、adb uninstall [-k] 卸载app&#xff0c;-k保留配置和参数 4、adb push 把本地文件上传设备 5、adb pull 下载文件到本地 6、cd D:\sdk\platform-tool…

ASP.NET Core--依赖注入(DI)--在ASP.NET Core中使用依赖注入

一、在ASP.NET Core中实现依赖注入 1.1 配置依赖注入 在ASP.NET Core中实现依赖注入的第一步是配置依赖注入。ASP.NET Core使用了一个称为依赖注入容器&#xff08;DI Container&#xff09;的组件来管理对象之间的依赖关系。DI容器在应用程序启动时被配置&#xff0c;并且可…

深度学习系列75:sql大模型工具vanna

1. 概述 vanna是一个可以将自然语言转为sql的工具。简单的demo如下&#xff1a; !pip install vanna import vanna from vanna.remote import VannaDefault vn VannaDefault(modelchinook, api_keyvanna.get_api_key(my-emailexample.com)) vn.connect_to_sqlite(https://va…

基于.Net Core+Vue的文件加密系统

1系统架构图 2 用例图 管理员角色的用例&#xff1a; 文件分享大厅&#xff1a;管理员可以访问文件分享大厅&#xff0c;下载文件。个人信息管理&#xff1a;管理员可以更新自己的个人信息&#xff0c;修改密码。用户管理&#xff1a;管理员负责创建、更新或删除用户账户&…