文章目录

  • 资源管理
    • 资源访问
      • 指向资源句柄或描述符的变量,在资源释放后立即赋予新值
      • lambda函数
        • 当lambda会逃逸出函数外面时,禁止按引用捕获局部变量
        • 避免lambda表达式使用默认捕获模式
    • 资源分配与回收
      • 避免出现delete this操作
      • 使用恰当的方式处理new操作符的内存分配错误
      • 合理选择值类型、智能指针、裸指针或引用
      • 使用RAII技术管理资源的生命周期
      • 使用`std::make_unique`而不是`new`创建`std::unique_ptr`
      • 使用`std::make_shared`而不是`new`创建`std::shared_ptr`
  • 标准库
    • 字符串
      • 1.string_view
      • 2.不要保存std::string类型的c_str和data成员函数返回的指针
      • 3.确保用于字符串操作的缓冲区有足够的空间容纳字符数据和结束符,并且字符串以null结束符结束
      • 4.避免使用atoi、atol、atoll、atof函数
    • 容器与迭代器
      • 1.确保容器索引或迭代器在有效范围
      • 2.使用有效的迭代器和指向容器元素的指针与引用
      • 3.在不需要修改迭代器指向的对象时,应使用const_iterator
      • 4.确保目的区间已经足够大或者在算法执行时可以增加大小
      • 5.如果需要删除容器中的元素,必须在std::remove、std::remove_if类算法之后调用容器的erase方法
    • 禁用rand函数产生用于安全用途的伪随机数
  • 并发与并行
    • std::thread和std::mutex与std::condition_variable不能拷贝,只能移动
    • 编写多线程程序必须避免数据竞争
    • 尽量缩短在临界区内停留的时间
    • 多线程程序中要特别留意对象的生命周期
    • 使用条件变量的wait方法时,必须外加条件判断,并在循环中等待
    • 不要直接调用mutex的方法
    • 使用C++语言和标准库的机制实现线程安全的单例初始化
    • 不要在信号处理函数中访问共享对象

资源管理

资源访问

  • 外部数据作为数组索引或者内存操作长度时,需要校验其合法性
  • 内存申请前,必须对申请内存大小进行合法性校验,防止申请0长度内存,或者过多地、非法地申请内存。
  • 在传递数组参数时,不应单独传递指针。当函数参数类型为数组(不是数组的引用)或者指针时,若调用者传入数组,则在参数传递时数组会退化为指针,其数组长度信息会丢失,容易引发越界读写等问题。
  • 禁止将局部变量的地址传递到其作用域外

指向资源句柄或描述符的变量,在资源释放后立即赋予新值

“指向资源句柄或描述符的变量”包括:指针、文件描述符、socket描述符以及其他指向资源的变量。

以指针为例,当指针成功申请了一段内存之后,在这段内存释放以后,如果其指针未立即设置为nullptr,也未分配一个新的对象,那这个指针就是一个悬空指针。
如果再对悬空指针操作,可能会发生重复释放或访问已释放内存的问题,造成安全漏洞。消减该漏洞的有效方法是将释放后的指针立即设置为一个确定的新值,例如:设置为nullptr。

对于全局性的资源句柄或描述符,在资源释放后,应该马上设置新值,以避免使用其已释放的无效值;对于只在单个函数内使用的资源句柄或描述符,应确保资源释放后其无效值不被再次使用。
【反例】

int* a = new int{1};
delete a;
...
delete a;  // 错误,会导致double free错误

【正例】

int* a = new int{1};
delete a;
...
a = nullptr; // 正确
delete a;  // 避免了内存重复释放

注:默认的内存释放函数针对空指针不执行任何动作。

【正例】
如下代码中,在资源释放后,对应的变量应该立即赋予新值。

Socket s = INVALID_SOCKET;
int fd = -1;
...
CloseSocket(s);
s = INVALID_SOCKET;
...
close(fd);
fd = -1;
...

lambda函数

当lambda会逃逸出函数外面时,禁止按引用捕获局部变量

如果一个 lambda 不止在局部范围内使用,禁止按引用捕获局部变量,比如它被传递到了函数的外部,或者被传递给了其他线程的时候。lambda按引用捕获就是把局部对象的引用存储起来。如果 lambda 的生命周期会超过局部变量生命周期,则可能导致内存不安全。

【反例】

void Foo()
{int local = 0;// 按引用捕获 local,当函数返回后,local 不再存在,因此 Process() 的行为未定义threadPool.QueueWork([&] { Process(local); });
}

【正例】

void Foo()
{int local = 0;// 按值捕获 local, 在Process() 调用过程中,local 总是有效的threadPool.QueueWork([local] { Process(local); });
}
避免lambda表达式使用默认捕获模式

lambda表达式提供了两种默认捕获模式:按引用(&)和按值(=)。
默认按引用捕获会隐式的捕获所有局部变量的引用,容易导致访问悬空引用。相比之下,显式的写出需要捕获的变量可以更容易的检查对象生命周期,减小犯错可能。
默认按值捕获会隐式的捕获this指针,实际等同于按引用捕获了成员变量。如果存在静态变量,还会让阅读者误以为lambda复制了一份静态变量。从C++20开始,通过[=]默认捕获this将变为deprecated的。所以,当lambda表达式中使用了类成员变量或静态变量时,不宜使用按值默认捕获模式。
因此,通常应当明确写出lambda需要捕获的变量,而不是使用默认捕获模式。
【反例】

auto Fun()
{int addend = 0;static int baseValue = 0;return [=]() {                                 // 实际上只复制了addend++baseValue;                               // 修改会影响静态变量的值return baseValue + addend;};
}

【正例】

auto Fun()
{int addend = 0;static int baseValue = 0;return [addend, value = baseValue]() mutable { // 使用C++14的捕获初始化一个变量++value;                                   // 不会影响Fun函数中的静态变量return value + addend;};
}

在 C++ 11 和更高版本中,Lambda 表达式(通常称为 Lambda)是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象(闭包)的简便方法。lambda表达式与任何函数类似,具有返回类型、参数列表和函数体。与函数不同的是,lambda能定义在函数内部。lambda表达式具有如下形式

[ capture list ] (parameter list) -> return type { function body }
  • capture list,捕获列表,局部变量对于lambda函数体是不可见的,需要通过捕获的方式获得。捕获只针对于lambda函数的作用域内可见的非静态局部变量。 lambda表达式可以直接使用静态变量,而不需要被捕获。lambda函数可以无条件访问全局变量、作用域内的静态变量。捕获可以分为按值捕获和按引用捕获。
  • parameter list,参数列表。从C++14开始,支持默认参数,并且参数列表中如果使用auto的话,该lambda称为泛化lambda(generic lambda);
  • return type,返回类型,这里使用了返回值类型尾序语法(trailing return type synax)。可以省略,这种情况下根据lambda函数体中的return语句推断出返回类型,就像普通函数使用decltype(auto)推导返回值类型一样;如果函数体中没有return,则返回类型为void。
  • function body,与任何普通函数一样,表示函数体

资源分配与回收

  • new和delete配对使用,new[]和delete[]配对使用
  • 自定义new/delete操作符需要配对定义,且行为与被替换的操作符一致

避免出现delete this操作

delete this操作是自己销毁自己,在此之后再访问到该对象的成员时,可能造成未定义行为。
【例外】
在资源管理器、生命周期管理器等场景中可以使用delete this操作,此时应满足在delete this 操作后不再提供任何能够访问到this的入口,并且被delete的对象是由普通的new分配的。

使用恰当的方式处理new操作符的内存分配错误

默认的new操作符在内存分配失败时,会抛出std::bad_alloc异常,而使用了std::nothrow参数的new操作符在内存分配失败时,会返回nullptr
因此,需要针对不同场景来处理new操作符的内存分配错误:

  • 对于不会返回nullptrnew操作,不要对返回值做空指针检查。如果new操作失败抛出异常,则不会执行后面的代码,因此检查空指针是多余的操作。
  • 对于可能会返回nullptrnew操作,与对待malloc等内存分配函数一样,需要对返回值做空指针检查,如:使用了std::nothrownew操作

合理选择值类型、智能指针、裸指针或引用

通用原则:

  • 使用值类型 T 或 unique_ptr 来表达独占所有权
  • 如果需要转移所有权,应使用智能指针,而不是使用T*或T&作为参数
  • 原生指针 T* 和引用 T& 不表达所有权概念
  • 不涉及所有权转移的场景,应优先使用T*或T&作为参数,而不是智能指针。例如:不应使用 const unique_ptr& 类型作为参数
  • 当函数的返回类型为T*时,应当表示一个位置,而非传递所有权。返回的指针所指向的对象必须在调用者的作用域内有效。如果返回值不可能为空,则优先返回引用

智能指针:

  • 使用 shared_ptr<T> 来表达共享所有权。如果资源只有一个所有者,应使用unique_ptr<T> 而不是shared_ptr<T>
  • 使用 unique_ptr<T> 作为函数的参数和返回值,代表所有权转移
  • 使用 shared_ptrunique_ptr 代替 auto_ptrauto_ptr在C++11中已标识为deprecated,在C++17中已去除
  • 使用智能指针时也需要注意对象的生命周期,例如:使用get()返回的指针时,1)如果智能指针释放了其管理的对象,则该指针变成了无效指针;2)不能使用该指针初始化另一个智能指针;…

函数参数:

  • 使用 T&& 或者 unique_ptr<T> 类型作为参数,代表这个资源的所有权是从外部移动进来的
  • 使用 T 类型做为参数,代表这个函数内部拥有资源的所有权,资源可能是拷贝或者移动进来的
  • 使用 unique_ptr<T>& 类型作为参数,代表这个函数可能重置这个 unique_ptr 的指向
  • 使用 shared_ptr<T> 类型作为参数,代表这个函数也是这个资源的其中一个拥有者
  • 使用 shared_ptr<T>& 类型作为参数,代表这个函数可能重置这个 shared_ptr 的指向
  • 使用 const T& 类型作为参数,代表这个函数对资源是只读的,且不管理资源的释放
  • 使用 T& 类型作为参数,代表这个函数对资源可读写,且不管理资源的释放
  • 使用 const T* 类型作为参数,代表这个参数可能为空,这个函数对资源是只读的,且不管理资源的释放
  • 使用 T* 类型作为参数,代表这个参数可能为空,这个函数对资源可读写

数组和字符串:

  • 使用 T*T& 作为参数,代表指向的是一个 T 元素,而不是一组元素。即便是指针指向一组元素中的其中一个,也不应使用指针算术运算指向其他元素。如果要表达指向的是一组元素,应明确表达这个区间的开始和结束
  • 如果是表达字符串类型,应优先使用 std::stringstd::string_view(C++17)或类似的自定义类型
  • 如果是表达固定大小的数组类型,应优先使用 std::arraystd::span(C++20)或类似的自定义类型

使用RAII技术管理资源的生命周期

RAII代表 resource acquisition is initialization。它可以用于避免手工资源管理的复杂性。

资源的获取和释放是成对操作(例如new/delete,fopen/fclose,lock/unlock 等),恰好能对应C++语言对称的构造函数和析构函数。利用C++对象的生命周期来管理资源的生命周期,是一种常见的策略。

使用std::make_unique而不是new创建std::unique_ptr

本条款适用于C++14及之后的版本。C++14开始增加了std::make_unique,提供与std::make_shared类似的方式构造unique_ptr
相对于先 new 出裸指针再构造 unique_ptr ,直接使用 make_unique 的优点有:

  • make_unique 可以更明确的避免裸指针和智能指针混用。
  • 使用 make_unique 更简洁。

【例外】
因为技术原因,希望使用 unique_ptr、又无法使用 make_unique 的,可以不使用 make_unique
目前的已知场景有:

  • 使用 make_unique 时,不支持自定义 deleter。在需要自定义 deleter 的场景,建议在自己的命名空间实现定制版本的 make_unique
  • 如果分配内存需要自定义的内存分配方式(如使用 placement new、nothrow版本的new等)的话,也没法直接使用 make_unique。一种实际的场景是如果想对 C 的变长结构体(尾项为灵活数组成员)使用 unique_ptr,需要先分配比结构体大小更大的内存空间,然后使用 placement new 来进行初始化操作。对于这种情况,建议把 unique_ptr 的创建封装到一个单独的函数里。
  • C++20 之前不能用 std::make_unique 对没有构造函数的 C 结构体进行聚合初始化。可以考虑使用下面的自定义版本。
// C++17 的 std::make_unique 不能调用聚合初始化(C++20 里已解决);下面的工具函数解决了这个问题
template <typename T, typename... Args>
std::unique_ptr<T> MakeUnique(Args &&... args)
{if constexpr (std::is_constructible_v<T, Args...>) {return std::unique_ptr<T>{new T(std::forward<Args>(args)...)};} else {return std::unique_ptr<T>{new T{std::forward<Args>(args)...}};}
}

使用std::make_shared而不是new创建std::shared_ptr

std::shared_ptr管理两个实体:

  • 控制块(存储引用计数,deleter等)
  • 管理对象

std::make_shared创建std::shared_ptr,会一次性在堆上分配足够容纳控制块和管理对象的内存。 而使用std::shared_ptr<SomeClass>(new SomeClass)创建std::shared_ptr,除了new SomeClass会触发一次堆分配外,std::shard_ptr的构造函数还会触发第二次堆分配,产生额外的开销。

【例外】
类似std::make_unique,因为技术原因,希望使用 shared_ptr、又无法使用 make_shared 的,可以不使用 make_shared

标准库

字符串

1.string_view

C++17开始增加了std::string_view类型,该类型可以减少字符串复制操作,提升程序性能。在C++17及之后的版本中,建议使用std::string_view表示字符串常量,在C++17之前可以使用C风格的字符串常量。

当函数参数为只读字符串时,在C++17及之后的版本中使用std::string_view类型。

void Fun(std::string_view str) {...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96192.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96192.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/96192.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“R语言+遥感”的水环境综合评价方法实践技术应用

专题一、R语言概述1.1 R语言特点&#xff08;R语言&#xff09;1.2 安装R&#xff08;R语言&#xff09;1.3 安装RStudio&#xff08;R语言&#xff09;&#xff08;1&#xff09;下载地址&#xff08;2&#xff09;安装步骤&#xff08;3&#xff09;软件配置1.4 第一个程序He…

【项目复盘】【四轴飞行器设计】驱动开发部分

由于在参加面试时总需要花时间一点一点的回忆自己的项目内容&#xff0c;故我打算直接写一系列的项目复盘博客&#xff0c;方便每次面试前的回忆。内容仅作分享交流&#xff0c;如有谬误欢迎指正。 本项目系列的文章目录如下&#xff1a; 【项目复盘】【四轴飞行器设计】驱动…

wpf之ComboBox

前言 wpf中ComboBox的应用非常广泛&#xff0c;本文就来介绍ComboBox在wpf中的应用。 1、非MVVM模式下 1.1 xaml添加元素<ComboBox x:Name"cbx_test1" SelectedIndex" 0" ><ComboBoxItem >小明</ComboBoxItem ><ComboBoxItem &g…

从零开始学AI——13

前言 夏天快要过去&#xff0c;本书也快接近尾声了。 第十三章 13.1 半监督学习 在此之前&#xff0c;我们讨论的所有学习范式都具有非常明确的边界条件&#xff1a; 监督学习&#xff1a;我们拥有大量带标签的数据样本(xi,yi)(x_i, y_i)(xi​,yi​)&#xff0c;目标是学习从输…

k8sday12数据存储(1/2)

目录 一、简单基本存储 1、EmptyDir 1.1概念 1.2作用 1.3配置文件 1.4测试 2、HostPath 2.1概念 2.2作用 2.3配置文件 2.4测试 ①、数据共享 ②、持久化存储 3、NFS 3.1概念 3.2作用 3.3NFS服务安装 ①、设置主节点为NFS服务器 ②、给副节点安装NFS客户端工…

Spring Framework 常用注解详解(按所属包分类整理)

在使用 Spring Framework 进行开发时&#xff0c;注解&#xff08;Annotation&#xff09;是实现 依赖注入&#xff08;DI&#xff09;、组件扫描、AOP 切面、事务管理 和 Web 请求映射 的核心手段。Spring 提供了丰富且结构清晰的注解体系&#xff0c;这些注解按照功能被组织在…

ROADS落地的架构蓝图

2 ROADS落地的架构蓝图 将ROADS体验从理念转化为现实&#xff0c;需要一套完整且自顶向下的架构蓝图作为支撑。华为的实践表明&#xff0c;数字化转型的成功依赖于多个架构层次的协同推进&#xff0c;而非单点技术的应用。该蓝图通常包含以下五个关键层次&#xff0c;每一层都承…

如何构建一个神经网络?从零开始搭建你的第一个深度学习模型

在深度学习的海洋中&#xff0c;神经网络就像一艘船&#xff0c;承载着数据的流动与特征的提取。而构建一个神经网络&#xff0c;就像是在设计这艘船的结构。本文将带你一步步了解如何使用 PyTorch 构建一个完整的神经网络模型&#xff0c;涵盖网络层的组织、前向传播与反向传播…

自学嵌入式第二十三天:数据结构(3)-双链表

一、strtokchar * strtok(char *s1,char *s2);截断字符串&#xff0c;在s1字符串中找到s2截取前一段返回&#xff0c;如需要再次截取剩余段&#xff0c;再使用此函数s1输入NULL即可&#xff1b;二、bzerobzero(char *p,size_t size);清零,从p地址开始&#xff0c;清零size个bit…

河南萌新联赛2025第六场 - 郑州大学

暑期集训已经接近尾声&#xff0c;一年六场的暑期萌新联赛也已经结束了&#xff0c;进步是比较明显的&#xff0c;从一开始的七八百名到三四百名&#xff0c;虽然拿不出手&#xff0c;但是这也算对两个月的集训的算法初学者的我一个交代。 比赛传送门&#xff1a;河南萌新联赛…

2-1.Python 编码基础 - 基础运算符(算术运算符、赋值运算符、比较运算符、逻辑运算符)

一、算术运算符 1、基本介绍编号运算符说明示例输出结果1两数相加10 20302-两数相减10 - 20-103*两数相乘&#xff0c;或者返回一个被重复若干次的字符串10 * 202004/两数相除10 / 200.55//两数相除并返回商的整数部分9 // 246%两数相除并返回余数10 % 507**幂运算10 ** 21002…

CMOS知识点 MOS管不同工作区域电容特性

知识点14&#xff1a;MOSFET的电容主要来源于其物理结构&#xff1a;栅氧层电容&#xff1a;栅极&#xff08;G&#xff09;与衬底&#xff08;B&#xff09;、沟道、源&#xff08;S&#xff09;、漏&#xff08;D&#xff09;之间隔着二氧化硅绝缘层&#xff0c;自然形成电容…

预测性维护+智能优化:RK3568+FPGA方案在储能行业的应用

在储能行业&#xff0c;RK3568FPGA方案通过预测性维护和智能优化技术&#xff0c;显著提升系统可靠性和经济性。该方案采用异构架构&#xff08;FPGA处理高速信号采集&#xff0c;RK3568负责策略计算与通信管理&#xff09;&#xff0c;实现微秒级响应和精准控制。‌26一、预测…

工业4.0时代,耐达讯自动化Profibus转光纤如何重构HMI通信新标准?“

在智能制造与工业4.0浪潮下&#xff0c;HMI&#xff08;人机界面&#xff09;作为设备与操作员之间的“桥梁”&#xff0c;承担着实时数据显示、设备监控及交互控制的核心职能。然而&#xff0c;传统Profibus总线在HMI连接中常因电磁干扰、传输距离限制等问题&#xff0c;导致画…

SpringClound——网关、服务保护和分布式事务

一、网关网络的关口&#xff0c;负责请求的路由、转发、身份验证server:port: 8080 spring:cloud:nacos:discovery:server-addr: 192.168.96.129:8848gateway:routes:- id: item-serviceuri: lb://item-servicepredicates:- Path/items/**,/search/**- id: user-serviceuri: lb…

【C++】模版(初阶)

目录 一. 函数模版 1. 格式 原理 2. 函数模版的实例化 二. 类模板 void Swap(int& left, int& right) {int temp left;left right;right temp; }void Swap(double& left, double& right) {double temp left;left right;right temp; }void Swap(char&…

InfluxDB 开发工具链:IDE 插件与调试技巧(二)

四、利用 IDE 插件提升开发效率 4.1 代码编写技巧 在使用安装了 InfluxDB 插件的 IDE 进行代码编写时&#xff0c;我们可以充分利用插件提供的代码导航和智能提示功能&#xff0c;来显著提高编写 InfluxDB 相关代码的效率和准确性。 以一个涉及多个 Measurement 和复杂查询条…

定制开发开源AI智能名片S2B2C商城小程序:场景体验新维度与四重目标达成

摘要&#xff1a;本文聚焦于定制开发开源AI智能名片S2B2C商城小程序&#xff0c;探讨其在场景体验领域的应用与价值。通过深入分析场景体验的最高境界——深体验、强认知、高传播、关系深化这四个目标&#xff0c;阐述该小程序如何凭借自身特性与功能&#xff0c;在商业场景中实…

开源 GIS 服务器搭建:GeoServer 在 Linux 系统上的部署教程

GeoServer 是一个开源的地理信息服务服务器&#xff0c;可以发布地图、矢量数据和栅格数据。 1. 更新系统 sudo apt update && sudo apt upgrade -y2. 安装 Java 11 GeoServer 需要 Java 运行环境&#xff0c;这里用 OpenJDK 11。 sudo apt install openjdk-11-jdk…

前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案

前端面试题详解与更多面试题 WebGLCesiumThree 1. 自我介绍 回答要点&#xff1a; 教育背景和工作经验技术栈和专长领域参与过的重点项目个人优势和学习能力职业规划 示例&#xff1a; “我是一名有前端开发经验的工程师&#xff0c;熟练掌握React、Vue等主流框架&#x…