读书笔记:《Effective Modern C++(C++14)》

news/2025/2/19 8:07:44/

Effective Modern C++(C++14)

GitHub - CnTransGroup/EffectiveModernCppChinese: 《Effective Modern C++》- 完成翻译

Deducing Types

  1. 模版类型推导
    1. 引用,const,volatile被忽略
    2. 数组名和函数名退化为指针
    3. 通用引用:
      1. 左值保持引用和const
      2. 右值忽略引用和const
  2. auto 类型推导:
    1. {} 初始化被auto推导为std::initializer_list
    2. 模版推导无法解析 {} 初始化
    3. 不可见的代理类可能会使auto从表达式中推导出“错误的”类型
  3. decltype 类型推导:
    1. 将decltype应用于变量名会产生该变量名的声明类型
    2. 返回decltype-》auto + decltype尾返回类型
    3. auto 与 decltype(auto)不同,decltype保留了auto忽略的左值引用
decltype(x+y) add(T x, U y){}; // 编译错误template <typename T, typename U>
auto add(T x, U y) -> decltype(x+y) 
{ return x+y; 
} template <typename T, typename U> // C++ 14
auto add(T x, U y) 
{ return x+y; 
}decltype(auto) f1()
{int x = 0;return x;                            //decltype(x)是int,所以f1返回int
}decltype(auto) f2()
{int x = 0;return (x);                          //decltype((x))是int&,所以f2返回int&
}
  1. 查看类型推导结果:
    1. 编译期:编译期报错诊断
    2. 运行时:
      1. typeid:依赖编译期解析,可能不正确
      2. Boost.TypeIndex
template<typename T>                //只对TD进行声明
class TD;                           //TD == "Type Displayer"
// 如果尝试实例化这个类模板就会引出一个错误消息,因为这里没有用来实例化的类模板定义。为了查看x和y的类型,只需要使用它们的类型去实例化TD:
TD<decltype(x)> xType;              //引出包含x和y
TD<decltype(y)> yType;              //的类型的错误消息template<typename T>
void f(const T& param)
{using std::cout;cout << "T =     " << typeid(T).name() << '\n';             //显示Tcout << "param = " << typeid(param).name() << '\n';         //显示
}                                                               //的类型#include <boost/type_index.hpp>template<typename T>
void f(const T& param)
{using std::cout;using boost::typeindex::type_id_with_cvr;//显示Tcout << "T =     "<< type_id_with_cvr<T>().pretty_name()<< '\n';    //显示param类型cout << "param = "<< type_id_with_cvr<decltype(param)>().pretty_name()<< '\n';
}

Modern C++:98 vs 11

  1. 构造,拷贝构造,拷贝复制
Widget w1;              //调用默认构造函数
Widget w2 = w1;         //不是赋值运算,调用拷贝构造函数
w1 = w2;                //是赋值运算,调用拷贝赋值运算符(copy operator=)
  1. 初始化:(),=,{}
    1. ():构造,无参构造和函数声明语义冲突,无参构造应该省略()
    2. =:拷贝构造,拷贝赋值
    3. {}:统一初始化,不允许类型变窄
  2. std::initializer_list 和 {} 初始化永远是最佳匹配
  3. () 初始化和 {} 初始化可能会有巨大不同:ex:std::vector
  4. 重载指针参数和整型参数:
    1. 0:int
    2. NULL:int or long
    3. nullptr:std::nullptr_t,可以隐式转换为指向任何内置类型的指针
  5. **typedef(C++98)和using(C++11):**using别名模版更简单,不需要::type和typename
template<typename T>                            //MyAllocList<T>是
using MyAllocList = std::list<T, MyAlloc<T>>;   //std::list<T, MyAlloc<T>>
MyAllocList<Widget> lw;                         //用户代码template<typename T>                            //MyAllocList<T>是
struct MyAllocList {                            //std::list<T, MyAlloc<T>>typedef std::list<T, MyAlloc<T>> type;      //的同义词  
};
MyAllocList<Widget>::type lw;                   //用户代码template<typename T>
class Widget {                              //Widget<T>含有一个
private:                                    //MyAllocLIst<T>对象typename MyAllocList<T>::type list;     //作为数据成员
}; template <class T> 
using remove_const_t = typename remove_const<T>::type;
template <class T> 
using remove_reference_t = typename remove_reference<T>::type;
template <class T> 
using add_lvalue_reference_t = typename add_lvalue_reference<T>::type; 
  1. 非限域Enum(C+98)和限域Enum(C++11):限域class enum不污染命名空间,都支持底层类型,但是Enum class不能隐式转换只能cast
enum Color { black, white, red };   //black, white, red在Color所在的作用域
auto white = false;                 //错误! white早已在这个作用域中声明enum class Color { black, white, red }; //black, white, red限制在Color域内
auto white = false;                     //没问题,域内没有其他“white”
Color c = white;                        //错误,域中没有枚举名叫white
Color c = Color::white;                 //没问题using UserInfo =                //类型别名,参见Item9std::tuple<std::string,     //名字std::string,     //email地址std::size_t> ;   //声望enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;                         //同之前一样
auto val = std::get<uiEmail>(uInfo);    //啊,获取用户email字段的值template<typename E>                //C++14
constexpr std::underlying_type_t<E>toUType(E enumerator) noexcept
{return static_cast<std::underlying_type_t<E>>(enumerator);
}
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
  1. 未定义私有声明(C++98)和deleted(C++11):
    1. 删除自动生成的函数:都可
    2. 删除隐式转换类型的函数重载:only deleted
template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
private:basic_ios(const basic_ios& );           // not definedbasic_ios& operator=(const basic_ios&); // not defined
};template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:basic_ios(const basic_ios& ) = delete;basic_ios& operator=(const basic_ios&) = delete;
};bool isLucky(int number);       //原始版本
bool isLucky(char) = delete;    //拒绝char
bool isLucky(bool) = delete;    //拒绝bool
bool isLucky(double) = delete;  //拒绝float和doubletemplate<>
void processPointer<void>(void*) = delete;
template<>
void processPointer<char>(char*) = delete;
class Widget {
public:template<typename T>void processPointer(T* ptr)
};
template<>                                          //还是public,
void Widget::processPointer<void>(void*) = delete;  //但是已经被删除了
  1. 显式override和final来实现重载和避免重载:C++11新增引用限定符
class Widget {
public:using DataType = std::vector<double>;DataType& data() &              //对于左值Widgets,{ return values; }              //返回左值DataType data() &&              //对于右值Widgets,{ return std::move(values); }   //返回右值
private:DataType values;
};
  1. 优先使用const_iterator而非iterator
  2. **begin,end,cbegin,rbegin的非成员函数通用性更强,**可用于原生数组
  3. **尽可能使用noexcept:**可以编译优化,noexcept函数可以调用异常中立函数
RetType function(params) noexcept;  //极尽所能优化	//C++11风格,没有来自f的异常
RetType function(params) throw();   //较少优化		//C++98风格,没有来自f的异常
RetType function(params);           //较少优化
  1. **const 常量,编译期不一定可知;constexpr 编译期常量,编译期可知;constexpr 函数值根据参数在编译期是否可知决定,**constexpr函数既可以是运行时也可以是编译期
  2. const成员函数中的mutable成员变量,需要mutex或atomic来保证线程安全
  3. 特殊成员函数:构造,析构,拷贝构造,拷贝赋值,移动构造,移动赋值
  4. **Role of Three:**自定义资源管理时,析构,拷贝构造,拷贝赋值同时自定义
  5. 移动操作仅当类没有显式声明移动操作,拷贝操作,析构函数时才自动生成
  6. 在自定义移动时,相应拷贝被deleted,在自定义析构时,拷贝不自动生成
  7. **置入emplace_back优于插入push_back:**避免了临时对象的创建和销毁

Smart Pointers

  • **unique_ptr:专有所有权,大小与原始指针相同,不占用额外内存资源,**完全拥有指向内容的所有权,只能移动不能复制,可以转化为shared_ptr
  • **shared_ptr:共享所有权,大小是原始指针二倍,包含引用计数,**引用计数在堆上,内存动态分配,原子操作增减
    • 使用maked_shared创建,或直接传递new指针,不要传递原始指针变量
    • 如果原始指针变量存在,且存在多个基于原始指针的shared_ptr,会存在多次析构的问题
  • **weak_ptr:从shared_ptr创建,不拥有所有权,**只能判断原指针是否过期,不能解引用
    • 获取shared_ptr:lock(),shared_ptr()
    • 避免shared_ptr循环引用无法释放
    • 作为缓存,观察者,只判断是否过期
  • make_shared, make_unique:
    • 异常安全,异常时不会导致内存泄漏
    • 对象内存和引用计数内存一次分配
    • 控制块和对象内存块绑定,存在weak_ptr时无法实际释放内存

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • Pimpl,Pointer to implementation:通过减少在类实现和类使用者之间的编译依赖来减少编译时间。(减少头文件中包含其他头文件的数量)
    • unique_ptr: 独占所有权,符合抽象,析构函数和移动拷贝函数的定义需要在不完整类型实现之后
    • shared_ptr:共享所有权,不符合抽象,运行时数据结构更大
// 原 header
class Widget() {                    //定义在头文件“widget.h”
public:Widget();private:std::string name;std::vector<double> data;Gadget g1, g2, g3;              //Gadget是用户自定义的类型
};// 新 header
class Widget {                          //仍然在“widget.h”中
public:Widget();~Widget();Widget(Widget&& rhs);               //只有声明Widget& operator=(Widget&& rhs);private:                                //跟之前一样struct Impl;std::unique_ptr<Impl> pImpl;
};// 新 cpp
#include <string>                   //跟之前一样,仍然在“widget.cpp”中struct Widget::Impl {};          //跟之前一样Widget::Widget()                    //跟之前一样
: pImpl(std::make_unique<Impl>())
{}Widget::~Widget() = default;        //跟之前一样Widget::Widget(Widget&& rhs) = default;             //这里定义
Widget& Widget::operator=(Widget&& rhs) = default;

Rvalue, Move & Forward

  • **move:**无条件将实参转化为右值,不一定移动构造,const变量无法移动时只能拷贝构造
  • **forward:**当且仅当实参为右值时,将其转化为右值
  • move 和 forward 都是** cast 操作,运行期无代码**
  • **通用引用:**T&&, auto&&,类型推导决定是左值引用还是右值引用
  • 函数返回值,传参,最后一次使用:右值引用使用move,通用引用使用forward
  • **返回值优化:**返回函数内的局部对象
    • 使用move移动操作来返回
    • 直接在返回值的内存中构建局部对象,消除拷贝操作
  • 通用引用参数的函数重载,优先级高于大部分函数
  • 完美转发的构造函数,non-const的优先级可能会大于拷贝构造和系统构造函数
  • 重载通用引用:
    • tag dispatch
template<typename T>
void logAndAdd(T&& name)
{logAndAddImpl(std::forward<T>(name),std::is_integral<typename std::remove_reference<T>::type>());
}template<typename T>                            //非整型实参:添加到全局数据结构中
void logAndAddImpl(T&& name, std::false_type)	//译者注:高亮std::false_type
{auto now = std::chrono::system_clock::now();log(now, "logAndAdd");names.emplace(std::forward<T>(name));
}std::string nameFromIdx(int idx);           //与条款26一样,整型实参:查找名字并用它调用logAndAdd
void logAndAddImpl(int idx, std::true_type) //译者注:高亮std::true_type
{logAndAdd(nameFromIdx(idx)); 
}
  • enable_if 约束
class Person  {                                         //C++14
public:template<typename T,typename = std::enable_if_t<                    //这儿更少的代码!std::is_base_of<Person,std::decay_t<T> //还有这儿>::value>                                    //还有这儿>explicit Person(T&& n);};
  • 引用折叠reference collapsing)。禁止声明引用的引用,但是编译器会在特定的上下文中产生这些。当编译器生成引用的引用时,引用折叠指导下一步发生什么。
    • 如果任一引用为左值引用,则结果为左值引用。否则(即,如果引用都是右值引用),结果为右值引用。
    • 折叠发生在四种情况下:模板实例化,auto类型推导,typedef与别名声明的创建和使用,decltype。
    • 通用引用就是在特定上下文的右值引用,上下文是通过类型推导区分左值还是右值,并且发生引用折叠的那些地方。
  • **移动语义的缺陷:**某些情况不存在,速度不如复制,非noexcept移动不可用
  • 完美转发perfect forwarding)意味着我们不仅转发对象,我们还转发显著的特征:它们的类型,是左值还是右值,是const还是volatile。
  • **完美转发失败:**花括号初始化,作为空指针的0或者NULL,仅有声明的整型static const数据成员,模板和重载函数的名字,位域。
template<typename T>
void fwd(T&& param)             //接受任何实参
{f(std::forward<T>(param));  //转发给f
}template<typename... Ts>
void fwd(Ts&&... params)            //接受任何实参
{f(std::forward<Ts>(params)...); //转发给f
}
  • 重载拷贝左值,移动右值-》完美转发-》直接传值移动:
    • 多一次额外的移动,适用移动开销低
      • 左值:拷贝+移动
      • 右值:移动+移动
    • 实现简单,不用重载,不用模版
class Widget {                                  //方法1:对左值和右值重载
public:void addName(const std::string& newName){ names.push_back(newName); } // rvaluesvoid addName(std::string&& newName){ names.push_back(std::move(newName)); }
private:std::vector<std::string> names;
};class Widget {                                  //方法2:使用通用引用
public:template<typename T>void addName(T&& newName){ names.push_back(std::forward<T>(newName)); }
};class Widget {                                  //方法3:传值
public:void addName(std::string newName){ names.push_back(std::move(newName)); }
};

Lambda

lambdas 和闭包类存在于编译期,闭包存在于运行时。

  • **按引用捕获:**捕获的变量超出生命周期,导致悬空引用
  • **按值捕获【默认】:**捕获指针,导致悬空引用;无法捕捉成员变量,只能捕捉this指针;无法捕捉static变量
  • **初始化捕获,可以捕获移动对象【移动捕获】:**c++11可以使用bind来模拟捕获移动对象
auto func = [pw = std::move(pw)]        //使用std::move(pw)初始化闭包数据成员{ return pw->isValidated()&& pw->isArchived(); };auto func = [pw = std::make_unique<Widget>()]   //使用调用make_unique得到的结果{ return pw->isValidated()          //初始化闭包数据成员&& pw->isArchived(); };
  • **泛型捕获:**捕获auto&&,C++11可以使用bind的完美转发来模拟泛型捕获
auto f = [](auto&& param){return func(std::forward<decltype(param)>(param));};auto f = [](auto&&... param){return func(std::forward<decltype(param)>(param)...);};
  • 使用lambda取代bind:
// lambda
auto setSoundL = [](Sound s) {using namespace std::chrono;using namespace std::literals;      //对于C++14后缀setAlarm(steady_clock::now() + 1h,	//C++14写法,但是含义同上s,30s);};// bind
using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);auto setSoundB = std::bind(static_cast<SetAlarm3ParamType>(setAlarm), // 无法处理类型重载,需要显式制定类型std::bind(std::plus<>(), steady_clock::now(), 1h), // 需要推迟表达式求值,避免在调用实际函数前求值_1,30s);

Concurrency API

Concurrency support library (since C++11) - cppreference.com

concurrency library

  • :Mutual exclusion
    • lock_guard,unique_lock
  • <condition_variable>

atomic & mutex

  • **atomic:**原子操作,一个CPU指令实现
  • **mutex:**互斥锁,可由atomic实现加锁解锁操作,未拿到锁需要陷入内核完成线程挂起
  • **自旋锁:**可由atomic实现,未拿到锁不会挂起线程,效率高于互斥锁

atomic & volatile

  • std::atomic用在并发编程中,对访问特殊内存没用。
    • 可以限制编译器或底层硬件对代码进行重排和乱序执行,在不使用互斥锁时,避免并发编程时的数据竞争问题
  • volatile用于访问特殊内存,对并发编程没用。
    • 内存映射IO时,编译器不会优化重复读写,冗余访问,无用存储等代码

thread, pthread & cpu cores

  • 硬件线程:
    • 物理CPU个数:个人电脑1个插槽,服务器2个插槽
    • 物理CPU核心数【物理CPU的芯片组数量】:双核,四核,八核
    • 逻辑核=虚拟核=硬件线程【超线程,同步多线程技术】:8核16线程
  • 软件线程:
    • 系统线程,操作系统线程,操作系统调度到硬件线程上执行的线程
    • pthread(POSIX thread),windows thread
  • std::thread:
    • C++对象,软件线程的句柄
    • joinable:正在运行
    • unjoinable:
      • 默认构造
      • 已被移动
      • join:运行完毕
      • detach:与软件线程之间的连接关系被打断

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

thread & future

  • 基于线程异步执行:std::thread t(doAsyncWork);
    • 不能直接访问异步执行的结果,如果执行函数有异常抛出,代码会终止执行。
    • 自己处理线程耗尽、资源超额、负载均衡、平台适配等问题
    • 可以访问pthread api,使用thread pool
  • 基于任务异步执行:auto fut = std::async(doAsyncWork);
    • 可以获取异步执行的结果和异常
    • 标准库处理线程耗尽、资源超额、负载均衡、平台适配等问题
    • 默认std::launch::async | std::launch::deferred, 不保证执行,异步执行, std::launch::async强制异步执行

thread disconstruction

析构joinable的thread会导致程序终止。

使用RAII(“Resource Acquisition Is Initialization,资源获得即初始化)包装thread,在析构时进行join。

class ThreadRAII {
public:enum class DtorAction { join, detach };ThreadRAII(std::thread&& t, DtorAction a): action(a), t(std::move(t)) {}~ThreadRAII(){if (t.joinable()) {if (action == DtorAction::join) {t.join();} else {t.detach();}}}std::thread& get() { return t; }private:DtorAction action;std::thread t;
};

future disconstruction

image.png
析构future会导致隐式的join或隐式的detach:

  • 引用了强制异步任务的共享状态的最后一个future,隐式join,在异步执行完成之前阻塞
  • 其他future,隐式detach

thread communication: condition variable,atomic & future

  1. **condition variable + mutex:**需要结合互斥锁,且需要重复验证避免虚假唤醒
std::condition_variable cv;         //事件的条件变量
std::mutex m;                       //配合cv使用的mutex//检测事件
cv.notify_one();                    //通知反应任务//反应的准备工作
{                                       //开启关键部分std::unique_lock<std::mutex> lk(m); //锁住互斥锁cv.wait(lk);                        //释放锁后等待通知,通知后再次加锁//对事件进行反应(m已经上锁)
}                                       //关闭关键部分;通过lk的析构函数解锁m//继续反应动作(m现在未上锁)
  1. **atomic:**基于轮询,而不是阻塞
std::atomic<bool> flag(false);          //共享的flag//检测某个事件
flag = true;                            //告诉反应线程//准备作出反应
while (!flag);                          //等待事件//对事件作出反应
  1. **future:**使用了堆内存存储共享状态,只能使用一次通信
std::promise<void> p;                   //通信信道的promise//检测某个事件
p.set_value();                          //通知反应任务//准备作出反应
p.get_future().wait();                  //等待对应于p的那个future//对事件作出反应

http://www.ppmy.cn/news/1255543.html

相关文章

DevEco Studio 调整开发工具中的字体大小与行高

我们打开编辑器 选择 左上角 File 下的 Settings 将左侧菜单栏 编辑 展开 我们在编辑下面 选择 Font 然后 如下图指向的两个位置 我们可以调整它的字体大小和行高 设置好之后 右下角 点击 Apply 应用 然后点击 OK即可 当然 你按着 Ctrl 然后鼠标滚动 也可以像浏览器那样 拉…

SSM项目实战-登录验证成功并路由到首页面,Vue3+Vite+Axios+Element-Plus技术

1、util/request.js import axios from "axios";let request axios.create({baseURL: "http://localhost:8080",timeout: 50000 });export default request 2、api/sysUser.js import request from "../util/request.js";export const login (…

【鸿蒙应用ArkTS开发系列】- 选择图片、文件和拍照功能实现

文章目录 前言创建多媒体Demo工程创建MediaBean 实体类创建MediaHelper工具类API标记弃用问题动态申请多媒体访问权限实现选择图片显示功能打包测试 前言 在使用App的时候&#xff0c;我们经常会在一些社交软件中聊天时发一些图片或者文件之类的多媒体文件&#xff0c;那在鸿蒙…

西南科技大学模拟电子技术实验四(集成运算放大器的线性应用)预习报告

一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) 反相比例运算电路(1)实验…

【读书笔记】微习惯

周日晚上尝试速读一本书《微习惯》&#xff0c;共七章看了下目录结构并不复杂&#xff0c;计划每章7-8分钟读完&#xff0c; 从20:15-21:00。读的时候&#xff0c;订下闹钟&#xff0c;催促着自己的进度。边读边记了一些要点和微信读书里面的划线。 第六章实践内容最为丰富&…

12.1平衡树(splay),旋转操作及代码

平衡树 变量定义 tot表示结点数量&#xff0c;rt表示根的编号 v[i]表示结点i的权值 fa[i]表示结点i的父亲节点 chi[i][2]表示结点i的左右孩子 cnt[i]表示结点i的权值存在数量&#xff0c;如1123&#xff0c;v[3]1&#xff0c;则cnt[3]2;就是说i3的三号结点的权值为1&…

Python标准库:math模块【侯小啾python领航班系列(十六)】

Python标准库:math模块【侯小啾python领航班系列(十六)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔…

用GPT4.0对csdn问答社区内容进行总结的对话记录(20231203号)

问题链接&#xff1a;微信小游戏开发2D碰撞检测问题 问题内容&#xff1a; 用户在开发微信小游戏的2D项目时遇到了碰撞检测无效的问题。他们尝试使用其他的碰撞类&#xff0c;但在2D中会报错。用户怀疑微信小游戏的框架可能不支持2D碰撞检测&#xff0c;或者需要自己计算和编…