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...
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...
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&...
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来显示类型信息,这种方法只有到了运行期才能使用,却可以对类型输出的格式提供完...
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却可以指向任何可调用...
Item18:使用独占指针管理具备专属所有权的资源
std::unique_ptr 的特性 轻量高效:默认删除器下,大小和原始指针一致,操作(如解引用)的执行效率和原始指针几乎无差别,可用于内存 / 性能敏感场景。 独占所有权:一个非空的 std::unique_ptr 独占指向的资源,仅可移动(move-only)、不可拷贝—— 拷贝会导致两个指针同时管理同一资源,析构时重复销毁;移动则会将所有权从源指针转移到目标指针(源指针置空)。 自动析构:析构时会自动销毁指向的资源(默认调用 delete),即便程序因异常、提前返回等非典型流程终止(除异常传播到 main 外、调用abort/exit等极端情况),也能保证资源释放,避免内存泄漏。 应用场景 工厂函数返回值: 适合作为继承体系对象的工厂函数返回类型(如 Investment 基类 + Stock/Bond 派生类),调用方独占工厂创建的堆对象所有权,无需手动管理销毁,示例中通过makeInvestment返回unique_ptr<Investment>,自动释放派生类对象。 接管 C 风格 API 的堆数组: std::un...
Item19:对于共享资源使用共享指针
定位与实现原理 功能:管理共享所有权的资源,兼具 “自动垃圾回收”(无需手动管理生命周期)和 “销毁可预测”(最后一个指向资源的 shared_ptr 销毁时,资源确定销毁)的优势,比纯垃圾回收语言的资源管理更通用、销毁时机更可控。 实现基础:引用计数(跟踪指向资源的 shared_ptr 数量): 拷贝 / 构造:递增引用计数; 析构 / 赋值指向其他对象:递减引用计数;计数归 0 时销毁资源; 移动更高效:移动 shared_ptr 无需修改引用计数(源指针置空),比拷贝快。 性能开销(对比原始指针 /unique_ptr): 大小:原始指针的 2 倍(存储 “指向资源的指针”+“指向引用计数的指针”); 额外成本:引用计数需动态分配(make_shared 可优化)、引用计数修改是原子操作(线程安全但比非原子操作慢)。 自定义删除器差异:删除器类型不是 shared_ptr 类型的一部分(unique_ptr 的删除器是类型一部分); 优势:不同删除器的同类型 shared_ptr(如 shared_ptr<Widget...
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; /...
Item21:优先使用make_unique和make_shared而非直接使用new
std::make_shared是 C++11 标准的,而std::make_unique直到 C++14 才加入; 如果用 C++11,可手写简易版make_unique(仅支持基础场景,不支持数组 / 自定义析构),但注意不要放到std命名空间,避免和 C++14 标准库冲突; 三个 make 函数:make_unique、make_shared、allocate_shared(allocate_shared和make_shared行为一致,只是支持自定义内存分配器),它们的共性是 “接收任意参数→完美转发给对象构造函数→动态分配对象→返回对应智能指针”。 优先用 make 函数避免类型重复直接用new创建智能指针时,类型会写两次,而 make 函数只需写一次,符合 “避免重复代码” 的软件工程原则: 1234567// 重复写Widget:不够简洁std::unique_ptr<Widget> upw2(new Widget);std::shared_ptr<Widget> spw2(new Widget);// 只写一次Widget:...
