TC++PL

2019-09-11
5 min read

C++ 程序设计语言(第 1 - 3 部分) / The C++ Programming Language / 第四版练习题 / C++11 Origin lib / 第三版源码 /

概念

graph TB
	A[ability]
	B[const<br/>constexpr]
	C[default ctor]
	D[RAII]
	E[concepts]
	F[variadic]
	G[ctor inherent]
	H[stream itr]
	I[thread]
	J[unique_lock<br/>lock_gurad]
	K[template]
	L[Class]
	M[future<br/>promise<br/>packaged_task]
	N[literal]
	P[alignof]
	Q[override<br/>new]
	R[attr<br/>noreturn]
	T[ODR]
	
	A-->B
	A-->E
	L-->C
	L-->D
	K-->F
	L-->G
	A-->H
	E-->I
	I-->J
	E-->L
	E-->K
	I-->M
	A-->N
	A-->P
	A-->T
	A-->R
	A-->Q
  1. const vs constexpr。[利用 constexpr 实现编译时双向映射](#constexpr mapping)

    const,编译器保证不会有代码直接修改变量,但编码人员可以使用特殊的技巧欺骗编译器从而修改变量

    constexpr,编译时求值的常量,可以放到内存的只读区域,const 保证这一点

  2. 不需要参数就能调用的构造函数被称为默认构造函数

  3. RAII,类似于 vector 这类资源管理类在构造的时候分配资源,在析构的时候释放资源

  4. 可变模板参数,区别对待第一个和其他参数,定义一个参数为空的函数来处理边界条件

  5. 构造函数继承:using vector<T>::vector。继承 vector 可以快速实现一个自动边界检查的 Vec(4.4.1)

  6. 流迭代器。流也有序列的概念,把迭代器用在流上面也是合理的。流迭代器的操作一般等价于对应的 IO 操作。假设 io_it 是输出流的迭代器,那么 *io_it=123cout << 123 是等价的

  7. 容器算法。标准库中大部分算法都是基于迭代器的,迭代器作为容器和算法之间的桥梁。直接将算法应用到容器对象上其实也是不错的选择,不过灵活性就差了一些

    namespace estd { // extend std
        template<typename C>
        void sort(C& c) { std::sort(c.begin(), c.end()); }
    };
    
  8. unique_lock,和 lock_guard 相比前者有更多的属性与功能,如下所示

    unique_lock<mutex> lck1 {m1,defer_lock}; // defer_lock: don’t yet try to acquire the mutex
    unique_lock<mutex> lck2 {m2,defer_lock};
    unique_lock<mutex> lck3 {m3,defer_lock};
    // ...
    lock(lck1,lck2,lck3); // acquire all three locks
    
  9. future && promise && packaged_task,期货、承诺与包装。包装用于将普通函数转换为future与promise的形式。async 函数是左边三个对象的封装

  10. 字面值,字符串字面值前缀 / 整数的前后缀 / 浮点数的前后缀 / User-defined literals /

    1. C++14 以后定义了若让非常方便的用户自定义字面值比如:24h / 60s / 60min / 28d 等等(std::literals::chrono_literals
  11. 重载 new 可以实现对象存储位置的指定

  12. C++11 以后提供了属性,例如 [[noreturn]] ,以供不同平台进行优化

  13. ODR,one define rule,一般情况下 C++ 中的类、变量、模板等只能定义一次,但对于类、模板和内联函数而言出现两次定义是可以的:他们出现在不同编译单元 && 代码逐单词对应 && 单词在不同编译单元含义一致

    1. ODR 是 header only 的基础
  14. 其他

变量的生命周期

graph TB
	A[Life of Obj]
	B[auto/free/temp]
	C[static]
	F[thread local]
	G[const& temp]
	H[ctor/dtor]
	J[default/delete]
	K[any declable]

	A-->B
	A-->C
	B-->G
	A-->F
	A-->H
	H-->J
	J-->K
	
	style K fill:#f9f,stroke:#333
  1. auto,栈空间变量
  2. static,全局静态,多线程场景下有竞争风险
  3. free,new/delete,堆空间变量
  4. temp,计算的中间结果、const 实参引用的对象等。temp 一般也是 auto 对象
  5. thread local,随线程创建而创建,随线程销毁而销毁

构造与析构

  1. 除构造函数外,类成员函数、变量不能与类名相同
  2. 注意自赋值检测
  3. 默认函数生成的原则如下(17.6):
    1. Big5,如果定义了其中一个,就应该设置其他 5 个,使用 default/delete
  1. If the programmer declares any constructor for a class, the default constructor is not generated for that class

  2. If the programmer declares a copy operation, a move operation, or a destructor for a class, no copy operation, move operation, or destructor is generated for that class

    Unfortunately, the second rule is only incompletely enforced: for backward compatibility, copy constructors and copy assignments are generated even if a destructor is defined. However, that generation is deprecated in the ISO standard (§iso.D), and you should expect a modern compiler to warn against it.

default && delete

delete 可以删除任何我们能声明的函数,如下:

  1. 禁止某些类型的模板特例化

    template<class T>
    T∗ clone(T∗ p) {
    	return new T{∗p};
    };
    Foo∗ clone(Foo∗) = delete; // don’t try to clone a Foo
    
  2. 禁止某些类型的自动类型转换

    struct Z {
    	Z(double); // can initialize with a double
    	Z(int) = delete; // but not with an integer
    };
    
  3. 其他

Operator

graph TB
	A[OPs]
	B[List Params]
	C[def ctor/init-list/Ctor params/Aggregate]
	F[ctor]
	G[def ctor]
	H[no params]
	I[init-list ctor]
	
	A-->B
	B-->C
	A-->F
	F-->G
	G-->H
	F-->I
	
	style C fill:#f9f,stroke:#333
  1. 列表可以用在三个地方,构造函数;聚合体初始化;初始化列表
    1. 优先尝试使用初始化列表构造;然后尝试普通构造函数;最后尝试聚合体构造
  2. 包含单一 std::initial_list 参数的构造函数被称为初始化列表构造函数默认构造与初始化列表构造优先

性能与资源

graph TB
	A[effiency]
	B[合理inline]
	C[pbr vs pbv]
	D[thread<br/>share vs pass]
	E[vector<br/>valarray]
	F[struct<br/>member]
	G[bigger<br/>header]
	H[bit-field]
	I[lambda]
	J[ns val]
	K[multable]
	L[this]
	M[init list]
	N[handle]
	
	
	A-->B
	A-->C
	A-->D
	A-->E
	A-->F
	F-->G
	F-->H
	A-->I
	I-->J
	I-->K
	I-->L
	A-->M
	M-->N
  1. inline,合理的选择类 inline 方法
  2. 合理的选择 pass-by-ref 和 pass-by-val,因为 CPU Cache,前者的实现效率并不一定比后者好
  3. 多线程领域里尽量使用 msg-passing 而不是 msg-sharing
  4. valarray 是专门用于向量计算的工具,其形式与 vector 类似,但对数值计算做了优化
  5. struct
    1. 如果想紧凑的存储结构体数据,把尺寸较大的成员放在前面;
    2. 位域(bit-field)是减少结构体占用内存空间的一个手段,因为使用位域的代码片段比常规代码长,所以只用在大量使用位域对象时才会节省内存空间
  6. lambda 表达式可以直接捕获 namespace 变量(包含全局变量),捕获类内成员时需要手动捕获 this
    1. 如果 lambda 什么也不捕获,则可以将 lambda 直接赋值给一个对应类型的函数指针
  7. 初始化列表对象(std::initial_list)是一个 handle ,内部有两个指针指向对象数组,且元素不可以修改
  8. 其他

依赖于实现的操作

类型转换

char c = 255;
// i 的值依赖于 char 的实现方式,如果 char 是有符号的则 i 为 -1;如果 char 无符号则 i 为 255
int i = c; 

异常

graph TB
	A[except]
	B[any class]
	C[copyable]
	D[time csm]
	E[Expected class]
	F[exception safe]
	G[B/S/N]
	H[dtor nthrow]
	I[scope_gurad<br/>finally]
	K[fun try block<br/>ctor try block]
	L[thread]
	M[call terminate]
	N[usage]
	O[except ptr]
	
	B-->C
	D-->E
	A-->F
	F-->G
	F-->H
	F-->I
	A-->K
	L-->M
	A-->N
	N-->L
	N-->D
	N-->B
	L-->O
  1. 异常可以是任意类型对象(甚至是 int),只要可以拷贝。异常在被捕获前可能被拷贝了多次

    1. 异常一般在抛出的时候被拷贝,所以异常对象有切片的可能
  2. 抛出异常与捕获异常的耗时是无法严格保证的,所以在嵌入式领域一般不使用异常机制

    1. 对时间非常敏感的函数可以使用异常的替代品,比如:https://github.com/TartanLlama/expected
  3. 异常抛出后依系统依旧处于有效状态,则称系统为异常安全

    1. 异常的三个层次:基本(Basic),不泄露资源;强保证(Strong),要么成功要么没影响;不抛异常(Nothrow)
    2. scope_guard 可以实现 java 中 finally 的功能
  4. C++提供了函数体 try 和构造函数 try

    int main() 
    try{
        ...
    }
    catch(...){
        ...
    }
    
  5. 可以使用 exception pointer 传输异常,std::current_exception()

  6. 其他

代码示例

概念

constexpr mapping

能写到代码中的配置一般都不会太多,所以直接使用线性查找速度也不慢,如果考虑到缓存(cache)的特性,相比于 map,数组查找会更快

using CchrpIPair = std::pair<const char*, int>;
constexpr CchrpIPair test_map_vec_g[] = {{"zero", 0},  {"one", 1},  {"two", 2},
                                         {"three", 3}, {"four", 4}, {"five", 5}};

int str2int(const char* ch_ptr) {
    auto end_it = std::end(test_map_vec_g);
    // 改变 find_if 中的 functor 就可以实现 int2str
    auto it = find_if(std::begin(test_map_vec_g), end_it, [ch_ptr](const CchrpIPair& p) {
        return std::strcmp(ch_ptr, p.first) == 0 ? true : false;
    });

    return it == end_it ? -1 : it->second;
}