Item11:优先选用删除函数,而非private未定义函数
主要考虑复制构造函数和复制赋值运算符:C++ 会自动生成这类特殊成员函数,但部分场景(如iostream的basic_ios类)中拷贝操作存在逻辑歧义(如流的读取状态、资源句柄拷贝混乱),因此需要主动禁止这些函数被调用。 C++98 禁止函数调用的方案:私有声明 + 未定义为阻止复制构造 / 赋值运算符被使用,C++98 采取 “声明为 private + 不定义” 的方式: 12345678template <class charT, class traits = char_traits<charT>>class basic_ios : public ios_base {public: …private: basic_ios(const basic_ios& ); // not defined basic_ios& operator=(const basic_ios&); // not defined}; 作用逻辑:private修饰阻止客户端代码调用;故意不...
Item14:只要函数不抛出异常就加上noexcept声明
C++11 的noexcept是替代 C++98 废弃异常说明的核心特性,作为函数接口的关键组成部分,仅当确定函数永远不会抛出异常时声明;其核心价值是让编译器生成更高效的目标代码,同时为调用者 / STL 提供明确的异常安全承诺,且不可为了加noexcept扭曲函数实现,大多数函数因 “异常中立” 无需声明。 noexcept的核心价值性能层面:编译器最大化优化代码 逻辑:noexcept承诺 “函数异常传播时可直接终止程序”,编译器无需保证调用栈完整展开、局部对象按构造反序析构,因此可剔除冗余代码,生成更精简高效的机器码; 对比差异:noexcept函数的优化度远高于 C++98 的throw()或无异常声明的函数(后两者需预留栈展开的冗余代码); 1234567891011121314// noexcept函数:编译器无冗余准备,极致优化void moveWidget(Widget&& w) noexcept { // 无栈展开、析构顺序记录的冗余代码,机器码更精简}// throw()函数:需预留栈展开准备,优化有限(...
Item16:让const成员函数线程安全
const 成员函数承诺 “不修改对象核心逻辑状态”,但常因mutable修饰的辅助变量(如多项式根的缓存rootVals、缓存有效标记rootsAreValid),在执行时修改这些变量; 多线程同时调用该 const 函数时,会出现 “不同线程读写同一块内存” 的数据竞争,导致程序行为未定义(即使逻辑上是 “读操作”)。 线程安全的解决方案1. 基础方案:使用std::mutex 适用场景:需要同步多个变量 / 多个操作(如缓存值 + 缓存有效标记),保证操作的原子性; 实现要点: 将std::mutex声明为mutable(因 const 成员函数中 mutex 被视为 const 对象,而锁 / 解锁操作会修改 mutex 状态); 用std::lock_guard自动加锁 / 解锁,避免手动解锁遗漏; 副作用:std::mutex不可拷贝 / 移动,导致包含它的类也不可拷贝 / 移动; 示例:多项式根缓存的线程安全实现(加锁后保证缓存计算 / 赋值的原子性)。 2. 轻量方案:使用std::atom...
Item17:理解特殊成员函数的生成机制
特殊成员函数的定义与分类特殊成员函数指 C++ 编译器会自动生成的成员函数,不同标准版本涵盖范围不同: C++98:包含 4 种:默认构造函数、析构函数、复制构造函数、复制赋值运算符 C++11 及以上:新增 2 种:移动构造函数、移动赋值运算符 编译器自动生成的特殊成员函数,访问层级为public、inline、非虚(除非基类析构函数为虚,派生类析构函数才会为虚) 大三律(Rule of Three)若声明了复制构造函数、复制赋值运算符、析构函数中的任意一个,就需要同时声明这三个。 需要改写复制操作,通常意味着类涉及裸资源管理(堆内存、文件句柄等): 一种复制操作中做的资源管理,大概率也需要在另一种复制操作中执行; 析构函数需参与资源管理(通常是释放资源)。 推论:若用户声明了析构函数,说明类需手动管理裸资源,而编译器默认的 “逐成员复制” 仅拷贝资源标识(如指针地址),而非资源本身,会导致内存泄漏、重复释放等资源管理混乱问题。 C++11 的补充规则 析构函数影响:只要用户声明了析构函数,编译器不会生成移动操作(移动构造 / 移动赋值); 复制操作的废弃行...
Item7:在创建对象时注意区分()和{}
初始化的基本方式指定初始化的方式包括使用小括号、使用等号,或是使用大括号: 123int x(0);int y = 0;int z{0}; 很多情况下使用一个等号和一对大括号也是可以的:(处理方式等同于只有大括号) 1int z = {0}; 对于用户定义的类型,等号的初始化方式实际并没有发生赋值: 123Widget w1; // 调用的是默认构造函数Widget w2 = w1; // 井非赋值,调用的是复制构造函数w1 = w2; // 并非赋值.调用的是复制赋值运算符 C++11引入了统一初始化:使用大括号进行初始化。 使用大括号来制定容器的初始内容: 1std: :vector<int> v{ 1, 3, 5 }; // v 的初始内容为1、3、5 为非静态成员指定默认初始化值(也可以使用“=”,但是()不行): 12345678class Widget { ... private: int x{ 0 }; // 可行,x 的默认值为 0...
Item8:优先选用nullptr,而非0或NULL
当C++在只能使用指针的语境中发现了0会把勉强解释为空指针,但是C++的基本观点还是0和NULL的类型是int,而非指针。 在C++98中,这样的观点可能在指针类型和整型之间进行重载时可能会发生意外: 1234567void f(int); // 整型版本void f(bool); // 布尔版本void f(void*); // 指针版本f(0); // 调用 f(int),而不是 f(void*)!f(NULL); // 可能通不过编译,但一般会调用 f(int)。从来不会调用 f(void*)f(nullptr); // 正确调用 f(void*) nullptr的优点它不具备整型类型。实际类型是std::nullptr_t,并且可以隐式转换到所有的裸指针类型。 提升代码的可读性: 1234567891011// 使用 0/NULL 的歧义auto result = findRecord();if (result == 0) { // result 是指针还是...
Item18:使用独占指针管理具备专属所有权的资源
std::unique_ptr 的特性 轻量高效:默认删除器下,大小和原始指针一致,操作(如解引用)的执行效率和原始指针几乎无差别,可用于内存 / 性能敏感场景。 独占所有权:一个非空的 std::unique_ptr 独占指向的资源,仅可移动(move-only)、不可拷贝—— 拷贝会导致两个指针同时管理同一资源,析构时重复销毁;移动则会将所有权从源指针转移到目标指针(源指针置空)。 自动析构:析构时会自动销毁指向的资源(默认调用 delete),即便程序因异常、提前返回等非典型流程终止(除异常传播到 main 外、调用abort/exit等极端情况),也能保证资源释放,避免内存泄漏。 应用场景 工厂函数返回值: 适合作为继承体系对象的工厂函数返回类型(如 Investment 基类 + Stock/Bond 派生类),调用方独占工厂创建的堆对象所有权,无需手动管理销毁,示例中通过makeInvestment返回unique_ptr<Investment>,自动释放派生类对象。 接管 C 风格 API 的堆数组: std::un...
Item9:优先选用别名声明,而非typedef
具有两种声明别名的方法: 123typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS; using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>; 别名声明在处理涉及函数指针的类型时比较容易理解: 12345//FP是一个指向函数的指针的同义词,它指向的函数带有int和const std::string&形参,不返回任何东西typedef void (*FP)(int, const std::string&); //typedef//含义同上using FP = void (*)(int, const std::string&); //别名声明 别名声明可以模板化(被称为别名模板),而typedef不行: 使用别名声明: 1234template<typename T> usin...
Item19:对于共享资源使用共享指针
定位与实现原理 功能:管理共享所有权的资源,兼具 “自动垃圾回收”(无需手动管理生命周期)和 “销毁可预测”(最后一个指向资源的 shared_ptr 销毁时,资源确定销毁)的优势,比纯垃圾回收语言的资源管理更通用、销毁时机更可控。 实现基础:引用计数(跟踪指向资源的 shared_ptr 数量): 拷贝 / 构造:递增引用计数; 析构 / 赋值指向其他对象:递减引用计数;计数归 0 时销毁资源; 移动更高效:移动 shared_ptr 无需修改引用计数(源指针置空),比拷贝快。 性能开销(对比原始指针 /unique_ptr): 大小:原始指针的 2 倍(存储 “指向资源的指针”+“指向引用计数的指针”); 额外成本:引用计数需动态分配(make_shared 可优化)、引用计数修改是原子操作(线程安全但比非原子操作慢)。 自定义删除器差异:删除器类型不是 shared_ptr 类型的一部分(unique_ptr 的删除器是类型一部分); 优势:不同删除器的同类型 shared_ptr(如 shared_ptr<Widget...
Item20:当shared_ptr可能悬空时使用weak_ptr
std::weak_ptr 是 std::shared_ptr 的辅助智能指针,不独立存在,特点如下: 不参与资源所有权共享,不影响指向对象的引用计数 能检测自身是否悬空(过期),即指向的对象是否已被销毁 无法直接解引用、无法直接判空,需借助 std::shared_ptr 实现安全访问 基础用法创建从 std::shared_ptr 直接创建,两者指向同一对象,且不会增加对象的引用计数: 123auto spw = std::make_shared<Widget>(); // 对象引用计数(RC)=1std::weak_ptr<Widget> wpw(spw); // RC仍为1,wpw与spw指向同一对象spw = nullptr; // RC=0,对象销毁,wpw悬空(过期) 安全访问对象(解决竞态条件)单独调用 expired() 判断是否过期存在竞态风险(检查后到访问前,对象可能被其他线程销毁),需通过原子操作同时完成 “检查 + 访问”,有两种方式: 方式 操作 效果 ...
