Item1:理解模板类型推导
函数模板形如: 12template<typename T> void f(ParamType param); 一次调用形如: 1f(expr); //以某表达式调用 f 在编译期,编译器会通过expr推导T的类型和ParamType的类型(通常会包含一些修饰,例如const或引用符号等限定)。 T的类型推导结果分以下三种情况讨论: 1. ParamType 具有指针或引用型别,但不是万能引用类型推导规则如下: 若expr具有引用类型,先将引用部分忽略。 然后,对expr的类型和ParamType的类型执行模式匹配,来决定T的类型。 举例模式声明如下: 12template<typename T> void f(T& param); // param 是个引用 声明下列变量: 123int x = 27; // x 的类型是 intconst int xx= x; // cx 的类型是 const intconst int& rx = x; // rx 是 x 的类型为 const int 的引用 各次调用的推导结果如下: 12...
Item3:理解 decltype 类型推导
个人认为原著写的非常晦涩难懂,所以精简如下: decltype用于告知名字或表达式的类型: 1234567891011121314151617181920212223const int i = 0; // decltype(i) 是 const intbool f(const Widget& w); // decltype(w) 是 const Widget&;decltype(f) 是 bool(const Widget&)struct Point { int x, y;}; // decltype(Point::x) 是 int;decltype(Point::y) 是 intWidget w; // decltype(w) 是 Widgetif (f(w)) ... // decltype(f(w)) 是 booltemplate<typename T&...
Item2:理解 auto 类型推导
推导方式和[[1. 理解模板类型推导]]一致,下面讨论不同点: 在初始化时,采用auto声明变量相比于采用固定类型声明变量更具优势。下列声明都能通过编译,但是结果却并不相同: 1234auto x1 = 27; // 类型是int,值是27auto x2(27); // 同上auto x3 = {27}; // 类型是std::initializer_list<int>,值是{27}auto x4{27}; //同上 当用于auto声明变量的初始化表达式是使用大括号时,推导所得类型就属于std::initializer_list<int>。如果类型推导失败(例如大括号里的值类型不一样),代码就不通过编译: 1auto x5 = {1,2,3.0}; // 错误,推导不出std::initializer_list<T>中的T 这里发生了两次类型推导: auto的使用:x5的类型需要推导,由于x5的初始表达式是用大括号,所以x5必须推导为一个std::initi...
Item4:掌握查看类型推导结果的方法
采用何种工具来查看型别推导结果,取决于在软件开发过程的哪个阶段需要该信息。三个可能的阶段:撰写代码阶段、编译阶段和运行时阶段。 IDE编辑器IDE 中的代码编辑器通常会在你将鼠标指针悬停至某个程序实体,如变量、形参、函数等时,显示出该实体的类型。例如: 1234const int theAnswer = 42;auto x = theAnswer;auto y = &theAnswer; IDE 编辑器很可能会显示出,x 的类型推导结果是 int, 而 y 则是 const int*。 但是如果遇到较为复杂的类型,IDE显示的信息就不太有用了。 编译器诊断信息一条有效途径是使用该类型导致某些编译错误。而报告错误的消息几乎肯定会提及导致该错误的类型。 例如: 12template<typename T> //只声明TD而不定义class TD; // TD是“类型显示类”的缩写 只要试图具现该模板,就会诱发一个错误信息,原因是找不到具现模板所需要的定义。 运行时输出使用 printf来显示类型信息,这种方法只有到了运行期才能使用,却可以对类型输出的格式提供完...
Item10:优先选用限域枚举,而非不限域枚举
使用限域enum来减少命名空间污染通用规则:如果在一对大括号里声明一个名字,则该名字的可见性限定在括号括起来的作用域内。 但这个规则不适用于 C++98 风格的枚举类型中定义的枚举变量:枚举变量的属于包含着这个枚举类型的作用域,此作用域内不能有其他实体取相同的名字。 12enum Color { black, white, red }; //black, white, red在Color所在的作用域auto white = false; //错误! white早已在这个作用域中声明 枚举量的名字泄露到枚举类型所在作用域的现象被称为:不限范围的枚举类型(未限域 enum)。 在 C++11 中,限定作用域的枚举类型(枚举类 / 限域 enum)不会以这样的方式泄露名字: 123456enum class Color { black, white, red }; //black, white, red限制在Color域内auto white = false; //没问题,域内没有其他“white”Color c = white; /...
Item12:使用override重写声明函数
面向对象编程的基本理念是:在派生类中虚函数实现,会改写基类中对应虚函数的实现。 虚函数重写的约束条件要想重写一个函数,必须满足下列要求: 基类中的函数必须是虚函数。 基类和派生类中的函数名字必须完全相同(析构函数例外)。 基类和派生类中的函数形参类型必须完全相同。 基类和派生类中的函数常量性必须完全相同。 基类和派生类中的函数返回值和异常规格必须兼容。 基类和派生类中的函数引用限定符必须完全相同。(C++11新增) 引用限定符:限定类的非静态成员函数只能被左值对象或右值对象调用。带&的成员函数仅允许左值对象调用;带&&的成员函数仅允许右值对象调用。 如果基类中的虚函数带有引用饰词,则派生类要对该函数进行改写版本必须也带有完全相同的引用饰词。否则相当于重新定义了一个新的函数。 编译器可能不报警但是存在错误的例子: 123456789101112131415class Base {public: virtual void mf1() const; virtual void mf2(int x); virtual void m...
Item6:当auto推导的类型不符合要求时,使用显式类型初始化习惯用法
个人认为原著写的非常难懂,所以精简总结如下: auto与代理类的问题当使用auto进行类型推导时,如果表达式返回的是代理类,auto会推导出代理类型而不是被代理的实际类型,可能导致未定义行为。 例如: 123std::vector<bool> features(const Widget& w);bool highPriority = features(w)[5]; // 含义是w具有高优先级吗?processWidget(w, highPriority); // 按照w的优先级来处理 这段代码没有什么问题,但是如果把highPriority从显式类型改成auto: 12auto highPriority = features(w)[5]; // 危险!推导出std::vector<bool>::referenceprocessWidget(w, highPriority); // 可能产生未定义行为! 原因: std::vector<bool>内部使用紧凑结构存储(每个bool占1位) 内存最小寻址单位是字...
Item5:优先使用 auto,而非显式类型声明
传统显示类型声明存在的问题1. 忘记初始化产生未知行为但是,auto声明的变量其类型推导自其初始化表达式,所以它们必须初始化: 123int x1; // 有潜在的未初始化风险 auto x2; // 编译错误!必须有初始化表达式auto x3 = 0; // 没问题, x 已经定义了 2. 类型冗长复杂,难以直接写出在泛型编程、容器操作等场景中,尝尝会出现冗长复杂的类型声明。如:typename std::iterator_traits<It>::value_type,可以直接使用auto代替。 12345// 传统显式声明:冗长繁琐,易出错 typename std::vector<int>::const_iterator it1 = vec.cbegin(); // auto 声明:简洁高效,无需记忆复杂类型 auto it2 = vec.cbegin(); 3. 类型不匹配导致意外的类型转换std::function是C++11标准库中的一个模板,它把函数指针的思想加以推广。函数指针只能指向函数,而std::function却可以指向任何可调用...
Item13:优先选用const_iterator,而非iterator
STL 的const_iterator等价于 “指向常量的指针(pointer-to-const)”,指向的值不可修改,符合 “能加const就加” 的通用编程准则 —— 只要无需修改迭代器指向的值,就应优先使用const_iterator;但该迭代器的实用性随 C++ 版本迭代大幅提升,C++11 是关键转折点,同时通用代码中需优先使用非成员函数版本的begin/end/cbegin等。 各 C++ 版本对 const_iterator 的支持差异1. C++98:支持残缺,实用性差const_iterator理论上符合规范,但实际使用存在两大痛点: 创建麻烦:非const容器无法直接获取const_iterator,需强制类型转换(或绑定到const引用)才能得到; 使用受限:insert/erase等容器操作仅接受iterator,且const_iterator无法可移植转换为iterator(即使reinterpret_cast也不行); 最终导致开发者被迫放弃使用,违背 “能const则const” 的准则。 123456789101112typedef s...
Item15:尽可能使用constexpr
constexpr作用于对象时,是加强版的const;但作用于函数时含义截然不同 —— 它并非简单限定 “返回 const 值” 或 “结果编译期可知”,而是根据调用场景动态适配。 constexpr对象:必然具备const只读属性,且必须由编译期可知的值完成初始化(技术上是翻译期,包含编译 + 链接,编程层面可视为编译期)。 constexpr函数:不保证返回值是 const,也不保证结果一定编译期可知—— 结果是否编译期确定,完全取决于传入的实参: 若实参是编译期已知的常量 → 结果在编译期计算,可用于 “整型常量表达式” 语境; 若实参是运行期才能确定的值 → 函数退化为普通函数,在运行期计算结果。 编译期可知的常量整型值,可用于 C++ 要求 “整型常量表达式” 的核心语境:数组尺寸、整型模板实参(如std::array的长度)、枚举量的值、对齐修饰符(alignas)等。普通const对象无法满足这些场景(仅保证只读,不保证编译期初始化),因此: 所有constexpr对象都是const对象,但并非所有const对象都是constexpr对象。 cons...
