
Effective Modern C+读书笔记
Effective Modern C+读书笔记
CCSUZB
吾生也有涯,而知也无涯
展开
-
条款34:优先选用lambda式,而非std::bind
称std::bind返回的函数对象为绑定对象。考虑在程序的某处我们想要设置在一小时后发出警报并持续30秒,考虑如下代码:using Time = std::chrono::steady_clock::time_point;enum class Sound {Beep, Siren, Whistle};using Duration = std::chrono::steady_clock::duration;// 在时刻t,发出声音s,持续时常dvoid setAlarm(Time t, Soun原创 2022-01-08 16:33:40 · 367 阅读 · 0 评论 -
条款33:对auto&&类型的形参使用decltype,以std::forward之
泛形lambda式是C++14的特性之一:lambda可以在形参中使用auto:auto f = [](auto x) { return func(normalize(x));};上述lambda式对x实施的唯一动作就是转发给normalize。如果normalize 区别对待左值和右值,则可以说lambda式撰写是有问题的。正确的撰写方式是把x完美转发给normalize,要求在代码中修改两处:1:x改为万能引用,2:使用std::forwardauto f = [](auto &&原创 2022-01-03 15:12:48 · 664 阅读 · 0 评论 -
条款32:使用初始化捕获对象將对象移入闭包
如果你想把一个只移对象(std::unique_ptr或std::future)放入闭包,C++11没有提供任何办法做到此事。而C++14为对象移动入闭包提供了支持。使用初始化捕获,则你会得到机会指定:由lambda生成的闭包类中的成员变量的名字一个表达式,用以初始化该成员变量考虑如下代码:class widget{public: bool isValidated() const; bool isProcessed() const; bool isArchived(原创 2022-01-02 15:04:11 · 96 阅读 · 0 评论 -
条款31:避免默认捕获模式
C++11中有两种默认捕获方式:按值或者按引用。按值的默认捕获方式可能会忽悠你,按引用的默认捕获方式可能导致空悬引用。按引用捕获会导致闭包包含涉及到局部变量的引用,或者涉及到定义lambda式子的作用域内形参的引用,考虑如下代码:using FilterContainer = std::vector<std::function<bool(int)>>;FilterContainer filters;void addDivisorFilter() { auto calc1原创 2021-12-26 21:13:03 · 377 阅读 · 0 评论 -
条款30:熟悉完美转发的失败情形
“转发”的含义不过是一个函数把自己的形参传递(转发)给另外一个函数而已。完美转发的含义是我们不仅转发对象,还转发其显著特征:类型,左值,右值,以及是否带有const或volation修饰词等。假设有某函数f,然后我们呢打算撰写一个函数將f作为转发目标,考虑如下代码:template<typename T>void fwd(T && param) { //接受任意实参 f(std::forward<T>(param)); //转发到f}给定目标函数f和原创 2021-12-25 18:50:42 · 580 阅读 · 0 评论 -
条款29:假定移动操作不存在、成本高、未使用
在标准C++11库中,所有容器都支持移动操作,但如果因此就断言所有的容器的移动都是成本低廉的,就错误了。考虑std::array这个C++11新容器类型,它实质上就是带有STL接的内建数组。其他标准容器都是將其内容放在堆上的,只需持有一个指涉到存放容器内容的堆内存指针。如如下vector而std::array缺少这样的指针,因为其内容数据是直接存储在对象内的,如下:注意:aw1中的元素是被移动入aw2的。无论是移动还是复制std::array类型的对象都还是需要线性时间的计算复杂度,因为容器中的每原创 2021-12-23 21:19:27 · 995 阅读 · 0 评论 -
条款27:熟悉万能引用类型进行重载的替代方案
标签分派一个万能引用形参通常会导致的后果是无论传入了什么都给出一个精确匹配结果,不过,如果万能引用仅是形参列表的一部分,该列表中还有其他非万能引用类型的形参的话,那么只要该非万能引用形参具备充分差的匹配能力,则它足以將这个带有万能引用形参的重载版本剔除出局。举例条款26的代码:std::multiset<std::string> names; // 全局数据结构void logAndAdd(const std::string &name) { auto now = std::c原创 2021-12-20 21:47:07 · 324 阅读 · 0 评论 -
条款26:避免万能引用类型进行重载
考虑如下代码:std::multiset<std::string> names; // 全局数据结构void logAndAdd(const std::string &name) { auto now = std::chrono::system_clock::now(); log(now, "logAndAdd"); names.emplace(name); // 將名字添加到全局数据结构中}std::string petName("Darla");logAnd原创 2021-12-18 16:54:26 · 552 阅读 · 0 评论 -
条款25:针对右值引用实施std::move,针对万能引用实施std::foraward
右值引用仅会绑定到那些可移动的对象上(临时对象)class Widget {public: Widget(Widget && rhs) : name(rhs.name), p(std::move(rhs.p)) {}; // 移动构造函数private: string name; shared_ptr<int> p;};万能引用只有在使用右值初始化时候才会强制转换成右值类型,正好是std::forward作用class Widget {pu原创 2021-12-13 22:10:29 · 351 阅读 · 0 评论 -
条款24:区分万能引用和右值引用
考虑如下代码:void f(Widget &&parm); // 右值引用Widget &&var1 = Widget(); // 右值引用auto &&var2 = var1; // 非右值引用template<typename T>void f(std::vector<T> &¶m); // 非右值引用template<typename T>void原创 2021-12-09 23:44:42 · 683 阅读 · 0 评论 -
条款23:理解std::move和std::forward
std::move无条件地将实参强制转化到右值,而std::forward则仅在某个特定条件满足时才执行同一个强制转换右值是可以移动的,所以在一个对象上实施了std::move,就是告诉编译器该对象具备可移动的条件。考虑如下类:class Annotation { explicit Annotation(std::string text); //待复制的形参,按值传递};Annotation的构造函数只需要读取text值,不需要修改,所以遵循“只要有可能就使用const就使用”的原则,修改代原创 2021-12-07 21:25:18 · 594 阅读 · 0 评论 -
条款22:使用Pimpl习惯用法时,将特殊成员函数的定义放到实现文件中
Pimpl手法:把某类的数据成员用一个指涉到某实现类/结构提的指针代替,然后把原来的主类中的数据成员放到实现类中,并通过指针间接访问这些数据成员;class Widget{public: Widget();private: std::string name; std::vector<double> data; Gadget g1, g2, g3; // Gadget是某种用户自定义类型};如果头文件gadget.h的内容发生了改变,则Widget的客户必原创 2021-11-27 16:17:53 · 596 阅读 · 0 评论 -
条款21:优先选用std::make_unique和std::make_shared,而非直接new
std::make_shared是C++11一部分,std::make_unique为C++14才引入,std::allocate_shared它的行为和std::make_shared一样,只不过它的第一个实参是个用以动态分配内存的分配器对象;是否对make系列函数来创建一个智能指针坐最平凡的对比,也能揭示优先选用make系列函数的第一个原因,考虑如下代码:auto upw1(std::make_unique<Widget>()); // 使用make系列函数std::unique_p原创 2021-11-23 22:20:46 · 360 阅读 · 0 评论 -
条款20:对于类似std::shared_ptr但有可能空悬的指针使用std::weak+ptr
std::weak_ptr并不是一种独立的智能指针,而是std::shared_ptr的一种扩充std::weak_ptr一般是通过std::shared_ptr来创建的。当使用std::shared_ptr完成初始化std::weak_ptr的时刻,两者就治涉到了相同的位置,但是std::weak_ptr并不影响所指涉到的对象的引用计数:auto spw = std::make_shared<Widget>(); // spw构造完成后,指涉到Widget的引用计数置为1std::we原创 2021-11-20 14:32:02 · 727 阅读 · 0 评论 -
条款19:使用`std::shared_ptr`管理具备共享所有权的资源
std::shared_ptr可以用过访问资源的引用计数来确定是否自己是最后一个指涉到该资源的。std::shared_ptr的构造函数会使改计数递增,而析构函数会使该计数递减,而赋值运算符同时执行两种操作。原创 2021-11-14 14:52:43 · 730 阅读 · 0 评论 -
条款18:使用std::unique_ptr管理具备专属所有权的资源
一个非空的std::unique_ptr总是拥有其所涉到的资源;std::unique_ptr不允许复制,因为如果复制了一个std::unique_ptr,就会得到两个指涉到同一资源的std::unique_ptr;假设我们有一个以Investment为基类的投资类型的继承体系:class Investment {...};class Stock: public Iverstment {...};class Bond: public Iverstment {...};class RealEstat原创 2021-10-24 13:19:55 · 147 阅读 · 0 评论 -
条款17:理解特种成员函数的生成机制
C++11当你声明一个类什么也不做时会默认生成如下函数:默认构造函数析构函数复制构造函数复制赋值运算符移动构造函数移动赋值运算符class Widget {public: Widget(Widget&& rhs); // 移动构造函数 Widget& operator=(Widget&& rhs);// 移动赋值运算符};移动构造函数将依照其形参rhs的各个非静态成员对于本类的对应成员执行移动构造移动复制运算符则将依照其形参rhs的原创 2021-10-12 22:27:18 · 109 阅读 · 0 评论 -
条款16:保证const成员函数的线程安全性
有一个类来表示多项式会非常方便,而在这个类中,其中有一个函数能够计算多项式的根,即那些使得多项式求值结果为零的数值;在计算多项式的根时,我们就把这些根缓存起来,并以返回缓存数值的手法实现rootsclass Polynomial {public: using RootsType = std::vector<double>; // 持有值的数据结构,这些数值使得多项式求值结果为零 RootsType roots() const { if (!rootsAreVa原创 2021-10-08 22:50:25 · 270 阅读 · 0 评论 -
条款15:只要有可能使用constexpr,就使用他
constexpr当它应用与对象时,其实就是一个加强版const;但是应用于函数时,却有着相当不同的意义;constexpr 对象:constexpr对象具备const属性,在编译阶段已知int sz; // 非constexpr变量constexpr auto arryaSize1 = sz; // 错误!sz的数值在编译器未知std::array<int, sz> data1; // 错误!sz的数值在编译器未知constexpr auto arraySize2 =原创 2021-10-03 16:04:28 · 106 阅读 · 0 评论 -
条款14:只要函数不会发射异常,就为其加上noexcept声明
原创 2021-10-02 11:47:33 · 268 阅读 · 0 评论 -
条款13:优先使用const_iterator,而非iterator
考虑如下代码:std::vector<int> values;std::vector<int>::iterator it = std::find(values.begin(), values.end(), 1983);values.insert(it, 1998);如果vector中并没有1983,那么插入位置将是末尾处。但在这里使用iterator并非正确选择...原创 2021-09-17 22:52:17 · 237 阅读 · 0 评论 -
条款12:为意在改写的函数添加override声明
正是虚函数改写,使得通过基类接口调用派生类函数成为可能:class Base {public: virtual void doWork(); // 基类中的虚函数 ];class Derived : public Base {public: virtual void doWork(); // 改写了Base::doWork};std::unique_ptr<Base> upb = std::makr_unique<Derived>(); // 创建基类指针原创 2021-08-28 15:52:13 · 117 阅读 · 0 评论 -
条款11:优先选用删除函数,而非private为定义函数
如果你写了代码给其他开发人员使用,并且你想阻止他们调用某个特定函数的话,那你只需要不要声明该函数即可。函数未经声明,不可调用。但是有时C++会替你声明函数;C++98中为了阻止这些函数被使用采取的做法是声明其为private,并且不去定义它们;举例来说,在接近C++标准的输入输出流库继承谱系基类部位的,是类模板basic_ios,对输入输出流进行复制是不可取的;在C++98中的basic_ios是像下面这样规定的:template<class charT, char tarits = char_t原创 2021-08-25 22:04:18 · 221 阅读 · 1 评论 -
条款10:优先选用限定作用域的枚举类型,而非不限定作用域的枚举类型
如果在一个大括号里面声明一个名字,则该名字的可见性就被限定在括号扩起来的作用域内。但是这个规则不适用于C++98风格的枚举类型中定义的枚举量;这些枚举量的名字属于包含着这个枚举类型的作用域,意味这在此作用域内不能有其他实体取相同的名字;enum Color {black, white, red}; // black,white,red所在作用域和Color相同auto white = false; // 错误!white已经在范围内被申明过这些枚举常量的名字泄漏到枚举类型所在作用域的这一事原创 2021-08-24 22:11:01 · 218 阅读 · 0 评论 -
条款8:优先选用nullptr,而非0或NULL
字面常量0的类型是int而非指针,当C++在只能使用指针的语境中发现一个0,它也会把它勉强解释为空指针。C++的基本观点还是0的类型为int,而非指针;从实际效果来说,以上结论对于NULL也成立(标准允许各个实现给予NULL非int的整数类型(如long))。C++98中,这样的基本观点可能在指针和整形之间i进行重载时可能会发生意外。void f(int);void f(bool);void f(void *);f(0); // 调用f(in)f(NULL);// 可能会通不过编译,但一般会调原创 2021-07-31 23:38:46 · 157 阅读 · 0 评论 -
条款7:在创建对象时注意区分`()`与`{}`
指定初始化值的方式包括使用小括号、使用等号、或使用大括号:int x(0); //初始化物在小括号内int y = 0; //初始化物在等号之后int x{0}; //初始化物在大括号之内对于int这样的内置类型来说,初始化和赋值无区别,但是对于用户自定义的类型,初始化和赋值就有区别了:Widget w1...原创 2021-05-02 14:15:48 · 444 阅读 · 1 评论 -
条款6:当auto推导的类型不符合要求时,使用带显示类型的初始化物习惯手法
有时候,你想往东,auto类型推导的结果偏偏往西。考虑如下代码:std::vector<bool> features(cosnt Widget& w);Widget w;bool highPriority = features(w)[5]; processWidget(w, hightPrioirty);上述代码没有什么问题,如果我们吧highPriority从显示类型改为auto呢?auto highPriority = featrues(w)[5];所有代码仍然可编原创 2021-03-14 14:45:50 · 266 阅读 · 0 评论 -
条款5:优先选用auto,而非显示类型声明
考虑如下代码:int x1; // 有潜在的未初始化风险auto x2; // 编译错误!必须有初始化物auto x3 = 0; // 没问题std::vector<int> v;unsigned sz = v.size();v.size()的返回值类型为std::vector<int>::size_type,在32位Windows上,unsigned和std::vector<int>::size_type的大小是一样的,但是在64位Window原创 2021-03-07 13:04:35 · 126 阅读 · 0 评论 -
条款4:掌握查看类型推导结果的方法
IDE编辑器IDE编辑器中的代码通常会在你将鼠标悬停至某个程序实体,如变量,形参,函数等时,显示出该实体的类型; const int theAnswer = 12; auto x = theAnwer; auto y = &theAnswer;IDE编辑器很可能显示出,x的类型推导为int,而y的推导类型为const int *编译器诊断信息:想要编译器显示推导出的类型,一条有效途径就是使用改类型导致的某些编译错误: template<typename T> //之声明原创 2021-02-28 13:40:59 · 175 阅读 · 0 评论 -
条款3:理解decltype
对于给定的名字或表达式,decltype能告诉你改名字或表达式的类型;考虑如下代码:const int i = 0; //decltype(i)是const intbool f(const Widget &w); // decltype(w)是const Widget & // decltype(f)是bool f(const Widget&)struct Point { // decltype(Point::x)是int int x,y; };原创 2021-02-27 20:04:15 · 169 阅读 · 0 评论 -
条款2:理解auto类型推导
在 采用auto进行变量声明中,类型饰词取代了ParamType,所以也存在三种情况情况 1: 类型饰词是指针或引用,但不是万能引用情况 2:类型饰词是万能引用情况3: 类型饰词既非指针也非引用auto x = 27; // 情况3const auto cx = x; // 情况3const auto& rx = x;// 情况1auto&& uref1 = x; // x的类型为int,且为左值,所以uref1的类型为int&auto&&am原创 2021-02-18 15:33:34 · 175 阅读 · 0 评论 -
条款1:理解模板类型推导
函数模板大致形式如下:template<typename T>void f(ParamType parem);调用形式如下:f(expr);在编译期,编译器会通过expr推导两个类型:一个是T的类型,另一个是ParamType的类型;其中T的类型推导结果不仅仅依赖expr的类型,还依赖ParamType的形式,而`ParamType分以下几种情况:ParamType是个指针或引用,但不是万能引用template<typename T>void f(T&原创 2021-02-18 13:52:28 · 170 阅读 · 0 评论