decltype

decltype 选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,而是总是以一个普通表达式作为参数,返回该表达式的类型,而且decltype并不会对表达式进行求值。

mutable

mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。

被const关键字修饰的函数的一个重要作用就是为了能够保护类中的成员变量。该函数可以使用类中的所有成员变量,但是不能修改他们的值。

在某些特殊情况下,我们还是需要在const函数中修改类的某些成员变量,因为要修改的成员变量与类本身并无多少关系,即使修改了也不会对类造成多少影响。


is_invocable

Name Description
std::is_invocable 确定是否能以参数 ArgTypes… 调用 Fn 。
std::is_invocable_r 确定是否能以参数 ArgTypes… 调用 Fn 并生成可转换为 R 的结果。
std::is_nothrow_invocable 确定是否能以参数 ArgTypes… 调用 Fn 并已知这种调用不抛任何异常。
std::is_nothrow_invocable_r 确定是否能以参数 ArgTypes… 调用 Fn 并生成可转换为 R 的结果 并已知这种调用(包括转换)不抛任何异常。

.inl 文件

内联函数较多的情况下,为了避免头文件过长,可以将所有的内联函数定义移到一个单独的文件中去,然后再用#include指令将它包含到类声明的后面(类的头文件的底部)。这样的文件称为一个内联函数定义文件。inl文件中也可以包含头文件的,因为内联函数中可能包含其他文件中定义的东西。


attribute

__attribute__ 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )。

__attribute__ 书写特征是:__attribute__ 前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的参数。

__attribute__ 语法格式为:attribute ((attribute-list))

其位置约束为:放于声明的尾部“ ;” 之前。

关键字__attribute__ 也可以对结构体(struct )或共用体(union )进行属性设置。

大致有六个参数值可以被设定,即:aligned, packed, transparent_union, unused, deprecated 和 may_alias 。

在使用__attribute__ 参数时,你也可以在参数的前后都加上“” (两个下划线),例如,使用aligned__而不是aligned ,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。

Name Description
aligned 设定一个指定大小的对齐格式。
packed 使用该属性对struct 或者union 类型进行定义,设定其类型的每一个变量的内存约束。
Function Attribute 把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。

__attribute__((constructor)) 在main() 之前执行, __attribute__((destructor)) 在main()执行结束之后执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>

static __attribute__((constructor)) void before() {

printf("Hello");
}

static __attribute__((destructor)) void after() {
printf(" World!\n");
}

int main(int args, char **argv) {
return EXIT_SUCCESS;
}

func

__func__ 返回所在函数名字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <string>
#include <iostream>

using namespace std;

const char *hello() { return __func__; }

const char *world() { return __func__; }

const char *darion() { return __func__; }

int main() {
cout << hello() << ", " << world() << " " << darion() << endl; // hello, world darion
}

_Pragma

#pragma 向编译器传递语言标准之外的一些信息。

头文件中定义 #pragma once,指示编译器该头文件只编译一次。

_Pragma 功能与 #pragma 类似:

1
_Pragma("once");

左值&右值

左值指的是既能够出现在等号左边也能出现在等号右边的变量或表达式

右值指的则是只能出现在等号右边的变量或表达式

有名字的变量就是左值,而由运算操作所产生的中间结果就是右值(1+2)

左值就是在程序中能够寻值的东西,右值就是没法取到它的地址的东西

对于基础类型,右值是不可被修改的,也不可被 const, volatile 所修饰

对于自定义的类型,右值却允许通过它的成员函数进行修改

左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体;

右值当在赋值号右边取出值赋给其他变量的值;右值可以在内存也可以在CPU寄存器。

右值引用 支持移动语义的实现, 利用移动语义,可以编写将资源从一个对象转移到另一个对象。


RAII

RAII全称是Resource Acquisition is Initialization,直译过来是资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。

智能指针(std::shared_ptrstd::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了。


Thread


noexcept

该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。

1
2
3
4
void swap(Type& x, Type& y) noexcept  //C++11
{
x.swap(y);
}
  • 有条件的noexcecpt
1
2
3
4
5
// 如果操作x.swap(y)不发生异常,那么函数swap(Type& x, Type& y)一定不发生异常。
void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))) //C++11
{
x.swap(y);
}

STL


SFINAE

Substitution failure is not an error: 匹配失败不是错误

当调用模板函数时编译器会根据传入参数推导最合适的模板函数,在这个推导过程中如果某一个或者某几个模板函数推导出来是编译无法通过的,只要有一个可以正确推导出来,那么那几个推导得到的可能产生编译错误的模板函数并不会引发编译错误。


Differences between std::make_unique and std::unique_ptr with new

make_unique is safe for creating temporaries, whereas with explicit use of new you have to remember the rule about not using unnamed temporaries.

1
2
3
foo(make_unique<T>(), make_unique<U>()); // exception safe

foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*

STL

  • 容器(container):常用数据结构,大致分为两类,序列容器,如vector,list,deque,关联容器,如set,map。在实现上,是类模板(class template)
  • 迭代器(iterator):一套访问容器的接口,行为类似于指针。它为不同算法提供的相对统一的容器访问方式,使得设计算法时无需关注过多关注数据。
  • 算法(algorithm):提供一套常用的算法,如sort,search,copy,erase … 在实现上,可以认为是一种函数模板(function template)。
  • 配置器(allocator):为容器提供空间配置和释放,对象构造和析构的服务,也是一个class template。
  • 仿函数(functor):作为函数使用的对象,用于泛化算法中的操作。
  • 配接器(adapter):将一种容器修饰为功能不同的另一种容器,如以容器vector为基础,在其上实现stack,stack的行为也是一种容器。这就是一种配接器。除此之外,还有迭代器配接器和仿函数配接器。

Memory Order

std::memory_order 指定内存访问,包括常规的非原子内存访问,如何围绕原子操作排序。在没有任何制约的多处理器系统上,多个线程同时读或写数个变量时,一个线程能观测到变量值更改的顺序不同于另一个线程写它们的顺序。其实,更改的顺序甚至能在多个读取线程间相异。一些类似的效果还能在单处理器系统上出现,因为内存模型允许编译器变换。

库中所有原子操作的默认行为提供序列一致顺序。该默认行为可能有损性能,不过可以给予库的原子操作额外的 std::memory_order 参数,以指定附加制约,在原子性外,编译器和处理器还必须强制该操作。

Option name Description
memory_order_relaxed 宽松操作:没有同步或顺序制约,仅对此操作要求原子性。
memory_order_consume 有此内存顺序的加载操作,在其影响的内存位置进行消费操作:当前线程中依赖于当前加载的该值的读或写不能被重排到此加载前。其他释放同一原子变量的线程的对数据依赖变量的写入,为当前线程所可见。在大多数平台上,这只影响到编译器优化。
memory_order_acquire 有此内存顺序的加载操作,在其影响的内存位置进行获得操作:当前线程中读或写不能被重排到此加载前。其他释放同一原子变量的线程的所有写入,能为当前线程所见。
memory_order_release 有此内存顺序的存储操作进行释放操作:当前线程中的读或写不能被重排到此存储后。当前线程的所有写入,可见于获得该同一原子变量的其他线程释放获得顺序),并且对该原子变量的带依赖写入变得对于其他消费同一原子对象的线程可见。
memory_order_acq_rel 带此内存顺序的读修改写操作既是获得操作又是释放操作。当前线程的读或写内存不能被重排到此存储前或后。所有释放同一原子变量的线程的写入可见于修改之前,而且修改可见于其他获得同一原子变量的线程。
memory_order_seq_cst 有此内存顺序的加载操作进行获得操作,存储操作进行释放操作,而读修改写操作进行获得操作和释放操作,再加上存在一个单独全序,其中所有线程以同一顺序观测到所有修改。

memory_order_acq_rel并不是一个内存模型,只是memory_order_acquirememory_order_release的合并,在一些读取并修改的操作中会用到。

memory_order_seq_cst / memory_order_seq_cst : seq_cstsequentially consistent的缩写,持严格的代码线性执行顺序。

memory_order_acquire / memory_order_release : 这种模型比memory_order_seq_cst稍微宽松一些,它移除了不相关的原子变量的顺序一致性。

memory_order_consume / memory_order_release : 这种内存模型又比memory_order_seq_cst更加宽松,它不仅移除了不相关的原子变量的顺序一致性,还移除了不相关普通变量的一致性。

memory_order_relaxed : 这种模型最宽松,他并不对任何顺序做保证,仅仅保证操作的原子性。

Relaxed ordering

std::atomicload()store()都要带上memory_order_relaxed参数。

仅仅保证load()store()是原子操作,除此之外,不提供任何跨线程的同步。

Release-Acquire ordering

store()使用memory_order_release,而load()使用memory_order_acquire

这种模型有两种效果,第一种是可以限制 CPU 指令的重排:

  • store()之前的所有读写操作,不允许被移动到这个store()的后面。
  • load()之后的所有读写操作,不允许被移动到这个load()的前面。

Sequential consistency有两个特性

  1. 所有线程执行指令的顺序都是按照源代码的顺序;
  2. 每个线程所能看到其他线程的操作的执行顺序都是一样的。

Acquire-release: 在同一个原子变量的release操作和acquire操作间同步,同时也就建立起了执行序列约束。所有的读和写动作不能移动到acquire操作之前。所有的读和写动作不能移动到release操作之后。

std::memory_order_consume

  1. carries-a-dependency-to:如果操作A的结果用于操作B的操作当中,那么A carries-a-dependency-to(将依赖带入) B
  2. dependency-ordered-before:如果操作B的结果进一步在相同的线程内被操作C使用,那么A的store操作是dependency-ordered-before(在依赖执行序列X之前)B的load操作(with std::memory_order_consume)。

SFINAE(Substitution Failure Is Not An Error) 从一组重载函数中删除模板实例化无效的函数

1
Prune functions that do not yield valid template instantiations from a set of overload functions.

In the process of template argument deduction, a C++ compiler attempts to instantiate signatures of a number of candidate overloaded functions to make sure that exactly one overloaded function is available as a perfect match for a given function call.

If an invalid argument or return type is formed during the instantiation of a function template, the instantiation is removed from the overload resolution set instead of causing a compilation error.
As long as there is one and only one function to which the call can be dispatched, the compiler issues no errors.

在对一个函数调用进行模板推导时,编译器会尝试推导所有的候选函数,以确保得到一个最完美的匹配。如果出现无效的模板参数,则会将该候选函数从重载决议集合中删除,只要最终得到了一个 perfect match ,编译就不会报错。

std::enable_if 主要作用就是当某个 condition 成立时,enable_if可以提供某种类型。但是当 condition 不满足的时候,enable_if<>::type 就是未定义的,当用到模板相关的场景时,只会 instantiate fail,并不会编译错误,

1
template<bool Cond, class T = void> struct enable_if;

alignas

alignas 说明符可应用于变量或非位域类数据成员的声明,或可应用于 class/struct/union 或枚举的定义。它不能应用于函数形参或 catch 子句的异常形参。

这种声明所声明的对象或类型的对齐要求,将等于用于该声明的所有 alignas 说明符中最严格(最大)的非零表达式,除非这会削弱类型的自然对齐。

若某个声明上的最严格(最大)alignas,比当它没有任何 alignas 说明符的情况下本应有的对齐更弱(即弱于其原生对齐,或弱于同一对象或类型的另一声明上的 alignas),则程序非良构:

无效的非零对齐,例如 alignas(3) 非良构。

同一声明上,弱于其他 alignas 的有效的非零对齐被忽略。

始终忽略 alignas(0)。


std::function 是一个可调用对象包装器,是一个类模板。它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。

std::function可以取代函数指针的作用,可以延迟函数的执行,适合作为回调函数使用。

std::bind 看作一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来适应原对象参数列表。

std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有两个作用:

  • 将可调用对象和其参数绑定成一个仿函数。
  • 只绑定部分参数,减少可调用对象传入的参数。(Carry化)
1
2
3
4
5
6
7
8
9
10
#include <iostream>

void print_sum(int n1, int n2) {
std::cout << n1 + n2 << '\n';
}

int main() {
std::function<void()> f = std::bind(&print_sum, 1, 1);
f();
}

reference_wrapper

reference_wrapper 将引用包装成一个对象,即引用的包装器;可以包裹一个指向对象或者指向函数指针的引用,既可以通过拷贝构造,也可以通过赋值构造;


size_t & ssize_t

size_t是一些C/C++标准在stddef.h中定义的,也是一个整型。size_t的真实类型与操作系统有关。size_t一般用来表示一种计数。sizeof操作符的结果类型是size_t,该类型保证能容纳实现所建立的最大对象的字节大小。

32位系统中被普遍定义为:typedef unsigned int size_t;为无符号整型,长度为4个字节。64位系统中定义为:typedef unsigned long size_t;为无符号长整型,长度为8个字节。

ssize_t这个数据类型用来表示可以被执行读写操作的数据块的大小.它和size_t类似,但必需是signed.意即:它表示的是signed size_t类型的。


Lambda表达式

Lambda 表达式用于定义并创建匿名的函数对象,形式如下:

1
2
3
4
[ capture-list ] { body }
[ capture-list ] ( params ) { body }
[ capture-list ] ( params ) -> ret { body }
[ capture-list ] ( params ) mutable exception attribute -> ret { body }

函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。

操作符重载函数参数

mutable 或 exception 声明

-> 返回值类型

{函数体} 标识函数的实现,不能省略,但可以为空。


万能引用(Universal Reference)

使用 T&& 类型的形参既能绑定右值,又能绑定左值。只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

template<typename T>
void func(T &&param) {
std::cout << param << std::endl;
}

int main() {
int num = 2019;
func(num);
func(2020);
return 0;
}

引用折叠(Universal Collapse)

一个模板函数,根据定义的形参和传入的实参的类型,可以有下面四中组合:

  • 左值-左值: 形参是左值引用,传入的实参是左值引用
  • 左值-右值: 形参是左值引用,传入的实参是右值引用
  • 右值-左值: 形参是右值引用,传入的实参是左值引用
  • 右值-右值: 形参是右值引用,传入的实参是右值引用

C++中不允许对引用再进行引用,所有的折叠引用最终都代表一个引用。

规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。


完美转发(Perfect Forwarding)

需要将一组参数原封不动的传递给另一个函数。

原封不动 不仅仅是参数的值不变,除了参数值之外,还有两组属性:左值/右值和 const/non-const。
完美转发就是在参数传递过程中,属性和参数值都不能改变,不产生额外的开销,就好像转发者不存在一样。


模板特化(template specialization)

模板偏特化(Partial Template Specialization)

模板特化是通过给模板中的所有模板参数一个具体的类的方式来实现的.

模板偏特化则是通过给模板中的部分模板参数以具体的类,而留下剩余的模板参数仍然使用原来的泛化定义的方式来实现的;


enable_shared_from_this

std::enable_shared_from_this 能让一个对象安全地生成其他额外的 std::shared_ptr 实例 ,它们与 pt 共享对象 t 的所有权。


元编程

元编程 通过操作程序实体,在编译时计算出运行时需要的常数、类型、代码的方法。

与普通程序不同,元编程则是借助语言提供的模板机制,通过编译器推导,在 编译时 生成程序。
元编程经过编译器推导得到的程序,再进一步通过编译器编译,产生最终的目标代码。

C++ 将模板分成了 4 类:类模板 (class template),函数模板 (function template),别名模板 (alias template) 和 变量模板 (variable template)。

前两者能产生新的类型,属于 类型构造器 (type constructor);而后两者是 C++ 为前两者补充的简化记法,属于 语法糖 (syntactic sugar)。

类模板函数模板 分别用于定义具有相似功能的 类 和 函数 (function),是泛型中对 类型 和 算法 的抽象。在标准库中,容器 (container) 和 函数 都是 类模板 和 函数模板 的应用。

别名模板变量模板 分别在 C++ 11 和 C++ 14 引入,分别提供了具有模板特性的 类型别名 (type alias) 和 常量 (constant) 的简记方法。

模板参数可以分为三种:值参数,类型参数,模板参数。

变长模板模板参数的个数可以不确定,变长参数折叠为一个 参数包,使用时通过编译时迭代,遍历各个参数。

特化 (specialization) 类似于函数的 重载 (overload),即给出 全部模板参数取值(完全特化)或 部分模板参数取值(部分特化)的模板实现。

实例化 (instantiation) 类似于函数的 绑定 (binding),是编译器根据参数的个数和类型,判断使用哪个重载的过程。由于函数和模板的重载具有相似性,所以他们的参数 重载规则 (overloading rule) 也是相似的。


Tips

Option name Description
auto for(auto x:range)会拷贝一份range元素,而不会改变range中元素
auto& 需要修改range中元素 使用for(auto& x:range)
const auto& 只读取range中元素时使用,for(const auto&x:range)不会进行拷贝,也不会修改range
const auto 需要拷贝元素,但不可修改拷贝出来的值时使用。for(const auto x:range)避免拷贝开销

compare_exchange_weakcompare_exchange_strong则是著名的CAS。参数会要求在这里传入期待的数值和新的数值。对比变量的值和期待的值是否一致,如果是,则替换为新数值。否则将变量的值和期待的值交换。

compare_exchange_weak版本的CAS允许偶然出乎意料的返回(比如在字段值和期待值一样的时候却返回了false),不过在一些循环算法中,这是可以接受的。通常它比起strong有更高的性能。

likely(x)代表x是逻辑真(1)的可能性比较大;unlikely(x)代表x是逻辑假(0)的可能性比较大。


Boost intrusive

intrusive 容器相当于是把构造函数使用的东西嵌入在对象里面,而 non-intrusive 容器实际上并不改变 object 本身,而是在外面 wrap 一层表示结构的东西。

boost.intrusive 提供了一个创建 intrusive 的框架,并且提供了一些类似 STL 中容器的实现。


std::chrono

std::chrono::duration 表示一段时间,比如两个小时,12.88秒,只要能换算成秒即可。

1
template <class Rep, class Period = ratio<1> > class duration;

std::chrono::time_point 表示一个具体时间,如上个世纪80年代、你的生日、今天下午、火车出发时间等。鉴于使用时间的情景不同,这个time point具体到什么程度,由选用的单位决定。一个time point必须有一个clock计时。

1
template <class Clock, class Duration = typename Clock::duration>  class time_point;

time_from_eproch()用来获得1970年1月1日到time_point时间经过的duration。如果time point以天为单位,函数返回的duration就以天为单位。

std::chrono::system_clock 表示当前系统时钟,系统中运行的所有进程使用now()得到的时间是一致的。

now() 当前时间time_point to_time_t() time_point转换成time_tfrom_time_t()time_t转换成time_point


restrict 含义是由编程者向编译器声明,在这个指针的生命周期中,只有这个指针本身或者直接由它产生的指针(如 ptr + 1)能够用来访问该指针指向的对象.作用是限制指针别名,帮助编译器做优化.如果该指针与另外一个指针指向同一对象,那么结果是未定义的.

表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;


__thread 关键字

__thread是GCC内置的线程局部存储设施。_thread变量每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。


std::decay

为类型T应用从左值到右值(lvalue to rvalue)、数组到指针(array to pointer)和函数到指针(function to pointer)的隐式转换。转换将移除类型T的cv限定符(const和volatile限定符),并定义结果类型为成员decay::type的类型。这种转换很类似于当函数的所有参数按值传递时发生转换。

  • 如果类型T是一个函数类型,那么从函数到指针的类型转换将被应用,并且T的衰变类型等同于:add_pointer<T>::type
  • 如果类型T是一个数组类型,那么从数组到指针的类型转换将被应用,并且T的衰变类型等同于:add_pointer<remove_extent<remove_reference<T>::type>::type>::type
  • 当左值到右值转换被应用时,T的衰变类型等同于:remove_cv<remove_reference<T>::type>::type

Reference:

  1. C++中的RAII介绍
  2. C++之左值、右值、左值引用、右值引用
  3. C++11 带来的新特性 (3)—— 关键字noexcept
  4. EFFECTIVE+STL中文版:50条有效使用STL的经验_11577044
  5. [c++11]我理解的右值引用、移动语义和完美转发
  6. C之attribute用法
  7. Differences between std::make_unique and std::unique_ptr with new
  8. cppreference: memory_order
  9. 理解 C++ 的 Memory Order
  10. C++11 中的内存模型
  11. C++11内存模型的粗略解释
  12. alignas 说明符 (C++11 起)
  13. C++中的万能引用和完美转发
  14. 浅谈 C++ 元编程
  15. C++模板元编程实战:一个深度学习框架的初步实现
  16. BOOST 的 INTRUSIVE 容器
  17. C++11朝码夕解: move和forward
  18. C++11 中的 emplace
  19. C++11 FAQ中文版
  20. 函数参数是右值引用类型,能够接受什么样的参数输入
  21. 理解 C++ 右值引用和 std::move
  22. C++11中的lambda,std::function以及std:bind
  23. C++中的值类别