Modern Effective C++ 条款1617:确保const成员函数线程安全条款特殊成员函数的生成

条款十六:确保const成员函数线程安全

C++ const成员函数表示该函数不会修改对象的状态。如果这些函数内部使用了可变(mutable)成员变量来缓存计算结果,那么它们在多线程环境中可能不是线程安全的。

例子中的Polynomial类有一个roots()方法,它是一个const成员函数,但会更新mutable成员rootsAreValid和rootVals来缓存根值。在没有同步的情况下,这些代码会有不同的线程读写相同的内存,会产生数据竞争。root成员函数虽然被声明为const,但不是线程安全的。

class Polynomial {
public:
    using RootsType = std::vector<double>;
    RootsType roots() const
    {
        if (!rootsAreValid) {               //如果缓存不可用
            …                               //计算根
                                            //用rootVals存储它们
            rootsAreValid = true;
        }
        return rootVals;
    }
private:
    mutable bool rootsAreValid{ false };    //初始化器(initializer)的
    mutable RootsType rootVals{};           //更多信息请查看条款7
};

const成员函数应当是线程安全的,以避免数据竞争。

解决方案

使用互斥锁(mutex)进行同步

通过在roots()方法中使用std::lock_guard<std::mutex>,可以确保在访问或更新缓存时只有一个线程能够执行相关代码段。使用std::mutex可以保护共享资源,防止多个线程同时修改相同的内存位置。

class Polynomial {
public:
    using RootsType = std::vector<double>;    
    RootsType roots() const{
        std::lock_guard<std::mutex> g(m); //锁定互斥量
        if (!rootsAreValid) {//如果缓存无效
           //计算/存储根值
            rootsAreValid = true;
        }
        return rootVals;//返回缓存的根值
    }
private:
    mutable std::mutex m;  // 互斥量
    mutable bool rootsAreValid { false };
    mutable RootsType rootVals {};
};

使用原子变量(Atomic)

对于仅涉及单个变量或内存位置的操作,可以使用std::atomic来替代互斥量,以减少同步开销。

std::atomic类型提供了一种更轻量级的方式来保证某些操作的原子性,适用于单个变量或内存位置的操作。但是,当需要对多个变量作为单元操作时,仍需使用互斥量,因为std::atomic无法保证跨多个变量的操作的原子性。

结论

const成员函数应当设计为线程安全,在预期会被多个线程并发调用的情况下。选择合适的同步机制(如std::mutex或std::atomic),取决于具体需求和性能考虑。当需要保证多个变量或内存位置的一致性时,应该使用互斥量而不是原子变量。

条款十七:理解特殊成员函数的生成

特殊成员函数是指C++自己生成的函数。C++98有四个:默认构造函数,析构函数,拷贝构造函数,拷贝赋值运算符。当然在这里有些细则要注意。默认构造函数仅在类完全没有构造函数的时候才生成。(防止编译器为某个类生成构造函数,但是你希望那个构造函数有参数)生成的特殊成员函数是隐式public且inline,它们是非虚的,除非相关函数是在派生类中的析构函数,派生类继承了有虚析构函数的基类。在这种情况下,编译器为派生类生成的析构函数是虚的。C++11特殊成员函数新增移动构造函数和移动赋值运算符。

class Widget {
public:
    …
    Widget(Widget&& rhs);               //移动构造函数
    Widget& operator=(Widget&& rhs);    //移动赋值运算符
    …
};

移动操作仅在需要的时候生成,如果生成了,就会对类的non-static数据成员执行逐成员的移动。

移动构造函数根据rhs参数里面对应的成员移动构造出新non-static部分,移动赋值运算符根据参数里面对应的non-static成员移动赋值。移动构造函数也移动构造基类部分(如果有的话),移动赋值运算符也是移动赋值基类部分。当对一个数据成员或者基类使用移动构造或者移动赋值时,没有任何保证移动一定会真的发生。逐成员移动,实际上,更像是逐成员移动请求,因为对不可移动类型(即对移动操作没有特殊支持的类型,比如大部分C++98传统类)使用“移动”操作实际上执行的是拷贝操作。

逐成员移动的核心是对对象使用std::move,然后函数决议时会选择执行移动还是拷贝操作。

关于拷贝和移动的一些规则

(1)拷贝操作(拷贝构造函数与拷贝赋值运算符)

如果声明了一个拷贝构造函数,但没有声明拷贝赋值运算符,编译器会自动生成拷贝赋值运算符;反之亦然。两个拷贝操作是独立的,一个的存在不会阻止另一个的自动生成。如果类没有任何用户定义的拷贝操作,编译器将为类生成默认的拷贝构造函数和拷贝赋值运算符。默认情况下,这两个函数都会执行逐成员的浅拷贝。

(2)移动操作(移动构造函数与移动赋值运算符)

移动构造函数和移动赋值运算符不是独立的。一旦显式声明了其中一个,编译器就不会再生成另一个。这是因为如果需要定制移动构造函数的行为,很可能移动赋值运算符也需要类似的处理。如果类没有任何用户定义的拷贝或移动操作,并且没有用户定义的析构函数,那么编译器可以自动生成移动构造函数和移动赋值运算符。默认情况下,这两个函数都会执行逐成员的移动操作,即调用每个成员的移动构造函数或移动赋值运算符。

(3)拷贝操作与移动操作的关系

拷贝抑制移动:如果一个类显式声明了拷贝构造函数或拷贝赋值运算符中的任何一个,那么编译器将不会自动生成移动构造函数和移动赋值运算符。如果用户定义了拷贝操作,逐成员拷贝不合适,那么逐成员移动也可能不合适。

(4)移动抑制拷贝

如果一个类显式声明了移动构造函数或移动赋值运算符中的任何一个,那么编译器将不会自动生成拷贝构造函数和拷贝赋值运算符。相反,它会将这些拷贝操作标记为delete,以防止通过拷贝进行对象的复制。这确保了如果逐成员移动不适合这个类,那么逐成员拷贝同样也不适合。

(5)对C++98代码的影响

C++11引入了移动语义,但是为了保持向后兼容性,C++98的代码不会因为新规则而被破坏。C++98标准中没有移动操作的概念,因此老代码不会受到影响。如果要让旧的C++98代码支持移动语义,需要使用C++11标准,并在类中添加相应的移动构造函数和移动赋值运算符。这样做之后,类就必须遵循C++11的特殊成员函数生成规则。

示例1: 使用 = default 显式声明特殊成员函数

class Widget {
public:
    ~Widget(); // 用户声明的析构函数
    Widget(const Widget&) = default; // 默认拷贝构造函数
    Widget& operator=(const Widget&) = default; // 默认拷贝赋值运算符
};

通过使用= default,明确表示希望使用编译器提供的默认实现。防止因添加新的功能(如日志记录)而无意间影响到特殊成员函数的自动生成,从而避免性能问题或其他意外行为。

示例2: 多态基类的特殊成员函数

class Base {
public:
    virtual ~Base() = default; // 虚析构函数
    Base(Base&&) = default; // 移动构造函数
    Base& operator=(Base&&) = default; // 移动赋值运算符
    Base(const Base&) = default; // 拷贝构造函数
    Base& operator=(const Base&) = default; // 拷贝赋值运算符
};

确保多态基类支持拷贝和移动语义。提供了完整的拷贝和移动语义支持,同时保持虚析构函数以正确处理派生类对象的删除。

示例3: 日志记录导致的性能问题

class StringTable {
public:
    StringTable() { makeLogEntry("Creating StringTable object"); }
    ~StringTable() { makeLogEntry("Destroying StringTable object"); }
private:
    std::map<int, std::string> values;
};

添加了用户定义的析构函数后,编译器不再生成移动构造函数和移动赋值运算符。移动操作退化为拷贝操作,导致性能下降,因为std::map的拷贝比移动慢得多。

解决方案:显式声明并使用= default来恢复移动操作。

class StringTable {
public:
    StringTable() { makeLogEntry("Creating StringTable object"); }
    ~StringTable() { makeLogEntry("Destroying StringTable object"); }
    StringTable(StringTable&&) = default; // 移动构造函数
    StringTable& operator=(StringTable&&) = default; // 移动赋值运算符
private:
    std::map<int, std::string> values;
};

总结:C++11对于特殊成员函数处理的规则如下:

  • 默认构造函数:和C++98规则相同。仅当类不存在用户声明的构造函数时才自动生成。
  • 析构函数:基本上和C++98相同,稍微不同的是现在析构默认noexcept(参见Item14)。和C++98一样,仅当基类析构为虚函数时该类析构才为虚函数。
  • 拷贝构造函数:和C++98运行时行为一样:逐成员拷贝non-static数据。仅当类没有用户定义的拷贝构造时才生成。如果类声明了移动操作它就是delete的。当用户声明了拷贝赋值或者析构,该函数自动生成已被废弃。
  • 拷贝赋值运算符:和C++98运行时行为一样:逐成员拷贝赋值non-static数据。仅当类没有用户定义的拷贝赋值时才生成。如果类声明了移动操作它就是delete的。当用户声明了拷贝构造或者析构,该函数自动生成已被废弃。
  • 移动构造函数移动赋值运算符:都对非static数据执行逐成员移动。仅当类没有用户定义的拷贝操作,移动操作或析构时才自动生成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/924502.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

零基础学安全--shell脚本学习(1)脚本创建执行及变量使用

目录 学习连接 什么是shell shell的分类 查看当前系统支持shell 学习前提 开始学习 第一种执行脚本方法 ​编辑 第二种执行脚本方法 第三种执行脚本方法 变量声明和定义 ​编辑 查看变量 删除变量 学习连接 声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣…

Java后端如何进行文件上传和下载 —— 本地版

简介&#xff1a; 本文详细介绍了在Java后端进行文件上传和下载的实现方法&#xff0c;包括文件上传保存到本地的完整流程、文件下载的代码实现&#xff0c;以及如何处理文件预览、下载大小限制和运行失败的问题&#xff0c;并提供了完整的代码示例。 大体思路 1、文件上传 …

Z2400024基于Java+SSM+mysql+maven开发的社区论坛系统的设计与实现(附源码 配置 文档)

基于SSM开发的社区论坛系统 1.摘要2.主要功能3.系统运行环境4.项目技术5.系统界面截图6.源码获取 1.摘要 本文介绍了一个基于SSM&#xff08;Spring、Spring MVC、MyBatis&#xff09;框架开发的社区论坛系统。该系统旨在打造一个高品质的开发者社区&#xff0c;为开发者提供一…

JAVA笔记 | 策略模式+枚举Enum简单实现策略模式(可直接套用)

本篇为更为简单的策略模式应用&#xff0c;使用枚举来进行策略分配 上一篇(链接如下)更像是策略工厂模式来分配策略 JAVA笔记 | 实际上用到的策略模式(可直接套用)-CSDN博客 先创建策略相关类 //策略类 public interface PetStrategy {/*** 执行动作 - 跑RUN*/String run(Str…

RabbitMQ 篇-深入了解延迟消息、MQ 可靠性(生产者可靠性、MQ 可靠性、消费者可靠性)

??博客主页&#xff1a;【_-CSDN博客】** 感谢大家点赞??收藏评论** 文章目录 ???1.0 RabbitMQ 的可靠性 ? ? ? ? 2.0 发送者的可靠性 ? ? ? ? 2.1 生产者重试机制 ? ? ? ? 2.2 生产者确认机制 ? ? ? ? 2.2.1 开启生产者确认机制 ? ? ? ? 2.2…

Redis(概念、IO模型、多路选择算法、安装和启停)

一、概念 关系型数据库是典型的行存储数据库&#xff0c;存在的问题是&#xff0c;按行存储的数据在物理层面占用的是连续存储空间&#xff0c;不适合海量数据存储。 Redis在生产中使用的最多的是用作数据缓存。 服务器先在缓存中查询数据&#xff0c;查到则返回&#xff0c;…

JAVA:Spring Boot 3 实现 Gzip 压缩优化的技术指南

1、简述 随着 Web 应用的用户量和数据量增加&#xff0c;网络带宽和页面加载速度逐渐成为瓶颈。为了减少数据传输量&#xff0c;提高用户体验&#xff0c;我们可以使用 Gzip 压缩 HTTP 响应。本文将介绍如何在 Spring Boot 3 中实现 Gzip 压缩优化。 2、配置 Spring Boot 3 对…

python期末复习

其他复习资料 Python期末复习-系列数据类型-CSDN博客 期末python复习-异常和函数-CSDN博客 期末Python复习-输入输出-CSDN博客 目录 一、面向对象程序设计 1.思维导图 2.基本概念 3.类对象和实例对象 3.1创建对象 3.2定义类中的成员变量 3.3类中属性的公有和私有 3.…

HDU Go Running(最小点覆盖 + 网络流优化)

题目大意&#xff1a;有一条无限长跑道&#xff0c;每个人可以规定自己跑步的方向&#xff0c;起点&#xff0c;跑步起止时间。每个人跑步的速度都是1m/s。最后从监控人员哪里得到了n个报告&#xff0c;每个报告给出了某人在某一时候所在的位置&#xff0c;问跑步的最少可能人数…

《用Python实现3D动态旋转爱心模型》

简介 如果二维的爱心图案已经无法满足你的创意&#xff0c;那今天的内容一定适合你&#xff01;通过Python和matplotlib库&#xff0c;我们可以实现一个动态旋转的3D爱心模型&#xff0c;充满立体感和动感。# 实现代码&#xff08;完整代码底部名片私信&#xff09; 以下是完…

Unity-Lightmap入门篇

&#xff1a;&#xff1a;这是一个实战文章&#xff0c;并没有知识分享&#xff0c;或理论知识&#xff1b;完全没有 关键字&#xff1a; “lightmap","全局光照”&#xff0c;“light Probe" (会混合一些中英文搜索&#xff0c;或者全英文搜索&#xff09; …

ElasticSearch通过es-head插件安装可视化及相关问题

1.es-head下载地址 GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster 2.启动 建议使用vscode启动&#xff0c;并安装好node.js环境 npm installnpm run start 通过http://localhost:9100就可以看到本地添加的es库 3.相关问题 3.1跨域问…

Android PMS(Package Manager Service)源码介绍

文章目录 前言一、PMS 启动流程二、APK 安装流程三、APK 卸载流程四、权限管理静态权限动态权限 五、 数据存储与一致性六、 PMS 的安全性策略1、权限检查2、签名认证3、动态权限管理4、应用安装验证5、保护系统目录 七、PMS 调试方法总结 前言 PackageManagerService&#xf…

OSPTrack:一个包含多个生态系统中软件包执行时生成的静态和动态特征的标记数据集,用于识别开源软件中的恶意行为。

2024-11-22 &#xff0c;由格拉斯哥大学创建的OSPTrack数据集&#xff0c;目的是通过捕获在隔离环境中执行包和库时生成的特征&#xff0c;包括静态和动态特征&#xff0c;来识别开源软件&#xff08;OSS&#xff09;中的恶意指标&#xff0c;特别是在源代码访问受限时&#xf…

Web登录页面设计

记录第一个前端界面&#xff0c;暑假期间写的&#xff0c;用了Lottie动画和canvas标签做动画&#xff0c;登录和注册也连接了数据库。 图片是从网上找的&#xff0c;如有侵权私信我删除&#xff0c;谢谢啦~

MySQL45讲 第29讲 如何判断一个数据库是不是出问题了?——阅读总结

文章目录 MySQL45讲 第二十九讲 如何判断一个数据库是不是出问题了&#xff1f;——阅读总结一、检测数据库实例健康状态的重要性二、常见检测方法及问题分析&#xff08;一&#xff09;select 1 判断法&#xff08;二&#xff09;查表判断法&#xff08;三&#xff09;更新判断…

mac下Gpt Chrome升级成GptBrowser书签和保存的密码恢复

cd /Users/自己的用户名/Library/Application\ Support/ 目录下有 GPT\ Chrome/ Google/ GptBrowser/ GPT\ Chrome 为原来的chrome浏览器的文件存储目录. GptBrowser 为升级后chrome浏览器存储目录 书签所在的文件 Bookmarks 登录账号Login 相关的文件 拷贝到GptBrow…

论文阅读笔记 | EEG:运动执行过程中的ERD

参考&#xff1a;https://mp.weixin.qq.com/s/RmcPSLv1ITMZZwqe2uZ_og?token1093147649&langzh_CN

Android U ART young cc流程分析

概述&#xff1a; 众所周知jvm虚拟机为了提高内存回收效率&#xff0c;更高效的进行内存管理与回收&#xff0c;对堆内存进行了分代管理比如hotspot虚拟机的新生代&#xff0c;老年代。根据各代的特征&#xff08; 新生代对象分配频繁而生存周期短&#xff0c;老年代生存周期长…

C++ 11重点总结1

智能指针 智能指针: C11引入了四种智能指针: auto_ptr(已弃用)、unique_ptr、shared_ptr和weak_ptr。智能指针可以更有效地管理堆内存,并避免常见的内存泄漏问题。 shared_ptr: 自定义删除器。 shared_ptr使用引用计数来管理它指向的对象的生命周期。多个shared_ptr实例可以指向…