std::jthread和停止令牌
1️⃣ 添加 std::jthread 的动机std::thread的缺点缺点一 std::thread要求在其生命周期结束时,若表示正在运行的线程,则调用join()(等待线程结束)或detach()(让线程在后台运行)。 若两者都没有调用,析构函数会立即导致异常的程序终止(在某些系统上导致段错误)。 由于这个原因,下面的代码会出现错误(除非不关心程序异常的终止) 1234void foo(){ std::thread t{task, name, val};} 当没有调用join()或detach()就表示正在运行的线程t在析构时,程序会调用std:terminate(),后者调用std::abort()。 缺点二 即使使用join()来等待正在运行的线程结束,任然会有一个严重的问题 123456void foo(){ std::thread t{task, name, val}; ... ...
解谜 extern "C"
1️⃣ 作用以及用途 extern "C" 的主要作用就是为了能够正确实现C++代码调用其他C语言代码。 加上 extern "C" 后,会指示编译器这部分的代码按C语言,而不是C++的方式进行编译。 2️⃣ 底层理由 由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名。 而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。 例如函数void fun(int, int)编译后的可能是 _fun_int_int—-不同编译器可能不同,但都采用了相似机制,用函数名和参数类型来命名编译后的函数名; 而C语言没有类似的重载机制,一般是利用函数名来指明编译后的函数名的,对应上面的函数可能会是_fun这样的名字。 因此,如果不加 extern "c",在链接阶段,链接器会从 moduleA 生成的目标文件 moduleA.obj 中找 _fun_int_int 这样的符号,显然这是不可能找到的,因为 fun()...
条款06:绝不在构造和析构过程中调用虚函数
1️⃣ 案例引入 假设你有个class继承体系,用来塑模股市交易如买进、卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志(audit log)中也需要创建一笔适当记录。 123456789101112131415161718192021222324class Transaction { //所有交易的 base classpublic: Transaction(); virtual void logTransaction() const = 0; //做出一份因类型不同而不同 //的日志记录(log entry)};Transaction::Transaction() // base class构造函数之实现{ ··· logTransaction(); // 最后动作是志记这笔交易};class BuyTransaction: public...
条款07:为多态基类声明成虚析构函数
1️⃣ 问题的引入 有许多种做法可以记录时间,因此,设计一个TimeKeeper base class和一些derived classes作为不同的计时方法: 1234567891011class TimeKeeper{public:TimeKeeper () ;~TimeKeeper () :...};class AtomicClock: public TimeKeeper { ... }; //原子钟class WaterClock: public TimeKeeper { ... }; //水钟class WristWatch: public TimeKeeper { ... }; //腕表 许多客户只想在程序中使用时间,不想操心时间如何计算等细节,这应该怎么办呢 ? 使用factory函数 返回指针指向一个计时对象。Factory函数会 ”返回一个base class指针,指向新生成之derived class对象” : 12TimeKeeper*...
条款04:确定对象被使用前已先被初始化
1️⃣ 问题引入 在不同的语境下,对象是否会被初始化时不一定的 ! int x; 如果 x是全局变量,则会被初始化为 0 ; 如果 x是局部变量,则不会被初始化。 类相关123456class Point{ int x , y;}···Point P; 同理的,p的成员变量有时候被初始化为 0,有时候不会。 为什么要初始化对象 ? 读取未初始化的值会导致不明确的行为。 在某些平台上,仅仅只是读取未初始化的值,就可能让你的程序终止运行。 2️⃣ 最佳处理方式 规则一:永远在使用对象之前就先将它初始化 对于内置类型,必须手工完成 ;对于类而言,初始化的责任就落在构造函数将对象的每一个成员都初始化。 规则二:总是在成员初值列中列出所有的变量,使得记住还有哪些成员变量。 3️⃣ 别混淆 “赋值” 和 “初始化” 案例引入12345678910111213141516171819202122class PhoneNumber {... };class ABEntry //ABEntry = "Address...
链接(link)
1️⃣ 概念 定义 链接(linking)是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载(复制)到内存并执行。 执行期 链接可以执行于编译时(compiletime),也就是在源代码被翻译成机器代码时。 链接可以执行于加载时(loadtime),也就是在程序被加载器(loader)加载到内存并执行时 链接可以执行于运行时(runtime),也就是由应用程序来执行。 执行对象 现代计算机,链接是由叫做链接器(linker)的程序自动执行的。 2️⃣ 编译器驱动程序 作用 大多数编译系统提供编译器驱动程序(compilerdriver),它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。 Linux 环境下,我们可以在 shell 中输入如下命令,调用 GCC 驱动程序 gcc -o prog main.c sum.c main.c 和 sum.c 分别为两个不同的源文件。 示例程序 12345678910int sum(int *a, int n);int array[2] = {1, 2};int...
条款05:了解C++默默编写时调用了什么函数
1️⃣ 问题引入 当定义一个 empty class 会发生什么? 编译器会为这个 empty class 自动声明: copy 构造函数 copy assignment 操作符 析构函数 同理,如果没有定义任何构造函数,编译器也会声明一个 default 默认构造函数。 2️⃣ 案例1class Empty {}; 等价于: 1234567class Empty {public: Empty() { ... } Empty(const Empty& rhs) { ... } Empty& operator=(const Empty& rhs) { ... } ~Empty() { ... }}; 3️⃣ 产生时机 只有这些函数被调用的时候,它们才会被编译器创建出来。 123Empty e1; // default构造函数 + 析构函数Empty e2(e1); //...
条款25:将 constructor和 non-member function 虚化
1️⃣ Virtual constructor virtual constructor听起来很荒谬,但是它们很有用。 2️⃣ 案例引入123456789101112131415161718192021class NLComponent { // 抽象基类,用于时事消息public: // 的组件(components), ... // 其中内含至少一个纯虚函数。};class TextBlock: public NLComponent {public: ... // 没有内含任何纯虚函数。};class Graphic: public NLComponent {public: ... // 没有内含任何纯虚函数。};class NewsLetter { //一份时事通信是由一系列的public: // NLComponent 对象构成的。 ...private: ...
条款24:了解 virtual function、multiple inheritance、virtual base class、runtime type identification 的成本
1️⃣ 问题的引入 当一个虚函数被调用时,执行的代码必须对应于调用者(对象)的动态类型。 对象的pointer或reference,其类型是无形的,那么编译器是如何有效率的实现这样的行为呢? 编译器采用的是 virtual table 和 virtual table pointer。 2️⃣ 虚表(virtual table) 定义 vtbl通常是一个由“函数指针”架构而成的数组。 程序中的每一个class凡声明(或继承)虚函数者,都有自己的一个vtbl,而其中的条目就是该 class的各个虚函数实现体的指针。 123456789101112class C1{public: C1(); virtual ~C1() ; virtual void f1() ; virtual int f2(char c) const; virtual void f3(const string& s); void f4 () const; ...}; C1虚表示意图 ...
条款26:限制某个class所能产生的对象的数量
1️⃣ 允许零个或一个对象 定义 每当即将产生一个对象,我们确知一件事情:会有一个constructor被调用。 “阻止某个class产出对象”的最简单方法就是将其 private 2️⃣ 问题的引入 假设我想为打印机设计一个class,我希望设下“只能存在一台打印机”的约束。 我们可以将”打印机对象“封装在某个函数内,如此一来每个人都能够取用打印机,但只有唯一一个打印机对象会被产生。 12345678910111213141516171819class PrintJob; //前置声明class Printer {public: void submitJob(const PrintJob& job); void reset (); void performSelfTest (); ... friend Printer& thePrinter();private: Printer(); Printer(const Printer& rhs);};Printer&...
