0%

Cpp-标准库

阅读更多

C++ Standard Library headers

1 algorithm

1.1 std::remove_if

std::remove_if用于将容器中满足条件的元素挪到最后,并返回指向这部分元素的起始迭代器,一般配合erase一起用

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

int main() {
std::vector<int> container{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
container.erase(std::remove_if(container.begin(), container.end(), [](int v) { return v % 2 != 0; }),
container.end());
for (const auto& v : container) {
std::cout << v << std::endl;
}
return 0;
}

2 any

std::any用于持有任意类型的对象,类似于Java中的java.lang.Object

  • std::any_cast用于将any对象转换成对应的类型。若类型错误则会抛出std::bad_any_cast

其实现方式也很直观,在堆上分配内存,用该分配的内存存储拷贝后的对象

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

struct Object {
Object() { std::cout << "Object()" << std::endl; }
Object(const Object& obj) { std::cout << "Object(const Object& obj)" << std::endl; }
Object(Object&& obj) { std::cout << "Object(Object&& obj)" << std::endl; }
};

int main() {
Object obj1;
Object obj2;
std::any a1 = obj1;
std::any a2 = std::move(obj2);
return 0;
}

3 atomic

3.1 内存一致性模型

3.1.1 Sequential consistency model

the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequence in the order specified by its program

Sequential consistency model(SC),也称为顺序一致性模型,其实就是规定了两件事情:

  1. 每个线程内部的指令都是按照程序规定的顺序(program order)执行的(单个线程的视角)
  2. 线程执行的交错顺序可以是任意的,但是所有线程所看见的整个程序的总体执行顺序都是一样的(整个程序的视角)
    • 即不能存在这样一种情况,对于写操作W1W2,处理器1看来,顺序是:W1 -> W2;而处理器2看来,顺序是:W2 -> W1

3.1.2 Relaxed consistency model

Relaxed consistency model也称为宽松内存一致性模型,它的特点是:

  1. 唯一的要求是在同一线程中,对同一原子变量的访问不可以被重排(单个线程的视角)
  2. 除了保证操作的原子性之外,没有限定前后指令的顺序,其他线程看到数据的变化顺序也可能不一样(整个程序的视角)

3.2 std::atomic

compare_exchange_strong(T& expected_value, T new_value)方法的第一个参数是个左值

  • 当前值与期望值expected_value相等时,修改当前值为设定值new_value,返回true
  • 当前值与期望值expected_value不等时,将期望值修改为当前值,返回false(这样更加方便循环,否则还得手动再读一次)
1
2
3
4
5
6
7
std::atomic_bool flag = false;
bool expected = false;

std::cout << "result: " << flag.compare_exchange_strong(expected, true)
<< ", flag: " << flag << ", expected: " << expected << std::endl;
std::cout << "result: " << flag.compare_exchange_strong(expected, true)
<< ", flag: " << flag << ", expected: " << expected << std::endl;
1
2
result: 1, flag: 1, expected: 0
result: 0, flag: 1, expected: 1

compare_exchange_weak(T& expected_value, T new_value)方法与strong版本基本相同,唯一的区别是weak版本允许偶然出乎意料的返回(相等时却返回了false),在大部分场景中,这种意外是可以接受的,通常比strong版本有更高的性能

3.3 std::memory_order

这是个枚举类型,包含6个枚举值

  • memory_order_relaxed
  • memory_order_consume
  • memory_order_acquire
  • memory_order_release
  • memory_order_acq_rel
  • memory_order_seq_cst

3.3.1 顺序一致次序(sequential consisten ordering)

memory_order_seq_cst属于这种内存模型

SC作为默认的内存序,是因为它意味着将程序看做是一个简单的序列。如果对于一个原子变量的操作都是顺序一致的,那么多线程程序的行为就像是这些操作都以一种特定顺序被单线程程序执行

该原子操作前后的读写(包括非原子的读写操作)不能跨过该操作乱序;该原子操作之前的写操作(包括非原子的写操作)都能被所有线程观察到

3.3.2 松弛次序(relaxed ordering)

memory_order_relaxed属于这种内存模型

在原子变量上采用relaxed ordering的操作不参与synchronized-with关系。在同一线程内对同一变量的操作仍保持happens-before关系,但这与别的线程无关

relaxed ordering中唯一的要求是在同一线程中,对同一原子变量的访问不可以被重排

3.3.3 获取-释放次序(acquire-release ordering)

memory_order_releasememory_order_acquirememory_order_acq_rel属于这种内存模型

memory_order_release用于写操作storememory_order_acquire用于读操作load

  • memory_order_release「原子操作之前的读写(包括非原子的读写)」不能往后乱序;并且之前的写操作(包括非原子的写操作),会被使用acquire/consume的线程观察到,这里要注意它和seq_cst不同的是只有相关的线程才能观察到写变化,所谓相关线程就是使用acquireconsume模式加载同一个共享变量的线程;而seq_cst是所有线程都观察到了
  • memory_order_acquire「原子操作之后的读写」不能往前乱序;它能看到release线程在调用load之前的那些写操作
  • memory_order_acq_relmemory_order_releasememory_order_acquire的合并,前后的读写都是不能跨过这个原子操作,但仅相关的线程能看到前面写的变化
  • memory_order_consumememory_order_acquire比较接近,也是和memory_order_release一起使用的;和memory_order_acquire不一样的地方是加了一个限定条件:依赖于该读操作的后续读写不能往前乱序;它可以看到release线程在调用load之前那些依赖的写操作,依赖于的意思是和该共享变量有关的写操作

看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-Thread 1-
n = 1
m = 1
p.store (&n, memory_order_release)

-Thread 2-
t = p.load (memory_order_acquire);
if (*t == 1)
assert(m == 1);

-Thread 3-
t = p.load (memory_order_consume);
if (*t == 1)
assert(m == 1);
  • 线程2的断言会成功,因为线程1对nm在store之前修改;线程2在load之后,可以观察到m的修改
  • 但线程3的断言不一定会成功,因为m是和load/store操作不相关的变量,线程3不一定能观察看到

3.4 参考

4 chrono

4.1 clock

三种时钟:

  1. steady_clock:是单调的时钟。其绝对值无意义,只会增长,适合用于记录程序耗时。
  2. system_clock:是系统的时钟,且系统的时钟可以修改,甚至可以网络对时。所以用系统时间计算时间差可能不准
  3. high_resolution_clock:是当前系统能够提供的最高精度的时钟。它也是不可以修改的。相当于steady_clock的高精度版本
1
2
3
auto start = std::chrono::steady_clock::now();
auto end = std::chrono::steady_clock::now();
auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();

5 functional

  1. std::function:其功能类似于函数指针,在需要函数指针的地方,可以传入std::function类型的对象(不是指针)
  2. std::bind
  3. std::mem_fn

5.1 参考

6 future

  1. std::promise
  2. std::future

7 iostream

  1. std::cout
  2. std::cin
  3. std::endl
  4. std::boolalpha
  5. std::noboolalpha

8 limits

  1. std::numeric_limits
    • std::numeric_limits<int32_t>::max()

9 memory

9.1 std::shared_ptr

类型转换

  • std::static_pointer_cast
  • std::dynamic_pointer_cast
  • std::const_pointer_cast
  • std::reinterpret_pointer_cast

只在函数使用指针,但并不保存对象内容

假如我们只需要在函数中,用这个对象处理一些事情,但不打算涉及其生命周期的管理,也不打算通过函数传参延长shared_ptr的生命周期。对于这种情况,可以使用raw pointer或者const shared_ptr&

1
2
3
void func(Widget*);
// 不发生拷贝,引用计数未增加
void func(const shared_ptr<Widget>&)

在函数中保存智能指针

假如我们需要在函数中把这个智能指针保存起来,这个时候建议直接传值

1
2
// 传参时发生拷贝,引用计数增加
void func(std::shared_ptr<Widget> ptr);

这样的话,外部传过来值的时候,可以选择move或者赋值。函数内部直接把这个对象通过move的方式保存起来

9.2 std::enable_shared_from_this

std::enable_shared_from_this能让一个由std::shared_ptr管理的对象,安全地生成其他额外的std::shared_ptr实例,原实例和新生成的示例共享所有权

  • 只能通过std::make_shared来创建实例(不能用new),否则会报错
  • 普通对象(非只能指针管理)调用std::enable_shared_from_this::shared_from_this方法,也会报错

有什么用途?当你持有的是某个对象的裸指针时(该对象的生命周期由智能指针管理),但此时你又想获取该对象的智能指针,此时就需要依赖std::enable_shared_from_this

  • 不能将this直接放入某个std::shared_ptr中,这样会导致delete野指针
1
2
3
4
5
6
7
8
9
10
class Demo : public std::enable_shared_from_this<Demo> {
};

int main() {
auto ptr = std::make_shared<Demo>();
auto another_ptr = ptr->shared_from_this();

std::cout << ptr << std::endl;
std::cout << another_ptr.get() << std::endl;
}

9.3 std::unique_ptr

9.4 参考

10 mutex

  1. std::mutex

  2. std::lock_guard

    • 直接使用std::mutex,如下面的例子。如果getVar方法抛出异常了,那么就会导致m.unlock()方法无法执行,可能会造成死锁
    1
    2
    3
    4
    mutex m;
    m.lock();
    sharedVariable= getVar();
    m.unlock();
    • 一种优雅的方式是使用std::lock_guard,该对象的析构方法中会进行锁的释放,需要将串行部分放到一个{}中,当退出该作用域时,std::lock_guard对象会析构,并释放锁,在任何正常或异常情况下都能够释放锁
    1
    2
    3
    4
    5
    {
    std::mutex m,
    std::lock_guard<std::mutex> lockGuard(m);
    sharedVariable= getVar();
    }
  3. std::unique_lock

  4. std::condition_variable

    • 调用wait方法时,必须获取监视器。而调用notify方法时,无需获取监视器

10.1 参考

11 numeric

  1. std::accumulate
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int main() {
    std::set<std::string> col = {"a", "b", "c"};

    std::string res = std::accumulate(std::begin(col),
    std::end(col),
    std::string(),
    [](const std::string &a, const std::string &b) {
    return a.empty() ? b
    : a + ", " + b;
    });

    std::cout << res << std::endl;
    }

12 optional

  1. std::optional

13 string

  1. std::string
  2. std::to_string

14 thread

如何设置或修改线程名:

  1. pthread_setname_np/pthread_getname_np,需要引入头文件<pthread.h>np表示non-portable,即平台相关
  2. prctl(PR_GET_NAME, name)/prctl(PR_SET_NAME, name),需要引入头文件<sys/prctl.h>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <pthread.h>
#include <sys/prctl.h>

#include <chrono>
#include <functional>
#include <iostream>
#include <sstream>
#include <thread>

void* change_thread_name(void* = nullptr) {
// avoid change name before set original thread name
std::this_thread::sleep_for(std::chrono::seconds(1));

char original_thread_name[16];
prctl(PR_GET_NAME, original_thread_name);
uint32_t cnt = 0;

while (true) {
if (cnt > 1000) {
cnt = 0;
}

std::stringstream ss;
ss << original_thread_name << "-" << cnt++;
prctl(PR_SET_NAME, ss.str().data());

char current_thread_name[16];
prctl(PR_GET_NAME, current_thread_name);
std::cout << current_thread_name << std::endl;

std::this_thread::sleep_for(std::chrono::seconds(1));
}
return nullptr;
}

void create_thread_by_pthread() {
pthread_t tid;
pthread_create(&tid, nullptr, change_thread_name, nullptr);
pthread_setname_np(tid, "pthread");
pthread_detach(tid);
}

void create_thread_by_std() {
std::function<void()> func = []() { change_thread_name(); };
std::thread t(func);
pthread_setname_np(t.native_handle(), "std-thread");
t.detach();
}

int main() {
std::this_thread::sleep_for(std::chrono::milliseconds(333));
create_thread_by_pthread();

std::this_thread::sleep_for(std::chrono::milliseconds(333));
create_thread_by_std();

prctl(PR_SET_NAME, "main");
change_thread_name();
}

15 type_traits

Standard library header <type_traits>

15.1 Helper Class

  1. std::integral_constant
  2. std::bool_constant
  3. std::true_type
  4. std::false_type

15.2 Primary type categories

  1. std::is_void
  2. std::is_null_pointer
  3. std::is_integral
  4. std::is_array
  5. std::is_pointer

15.3 Composite type categories

  1. std::is_fundamental
  2. std::is_arithmetic
  3. std::is_scalar
  4. std::is_reference
  5. std::is_member_pointer

15.4 Type properties

  1. std::is_const
  2. std::is_volatile
  3. std::is_final
  4. std::is_empty
  5. std::is_abstract

15.5 Supported operations

  1. std::is_constructible
  2. std::is_copy_constructible
  3. std::is_assignable
  4. std::is_copy_assignable
  5. std::is_destructible

15.6 Property queries

  1. std::alignment_of
  2. std::rank
  3. std::extent

15.7 Type relationships

  1. std::is_same
  2. std::is_base_of

15.8 Const-volatility specifiers

  1. std::remove_cv
  2. std::remove_const
  3. std::remove_volatile
  4. std::add_cv
  5. std::add_const
  6. std::add_volatile

15.9 References

  1. std::remove_reference
  2. std::add_lvalue_reference
  3. std::add_rvalue_reference

15.10 Pointers

  1. std::remove_pointer
  2. std::add_pointer

15.11 Sign modifiers

  1. std::make_signed
  2. std::make_unsigned

15.12 Arrays

  1. std::remove_extent
  2. std::remove_all_extents

15.13 Miscellaneous transformations

  1. std::enable_if
  2. std::conditional
  3. std::void_t
  4. std::decay

15.14 Alias

using template,用于简化上述模板。例如std::enable_if_t等价于typename enable_if<b,T>::type

  1. std::enable_if_t
  2. std::conditional_t
  3. std::remove_reference_t
  4. std::result_of_t
  5. std::invoke_result_t

15.15 std::move

标准库的实现如下:

1
2
3
4
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

本质上,就是做了一次类型转换,返回的一定是个右值

15.16 std::forward

std::forward主要用于实现模板的完美转发:因为对于一个变量而言,无论该变量的类型是左值引用还是右值引用,变量本身都是左值,如果直接将变量传递到下一个方法中,那么一定是按照左值来匹配重载函数的,而std::forward就是为了解决这个问题。请看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

void func(int &value) {
std::cout << "left reference version, value=" << value << std::endl;
}

void func(int &&value) {
std::cout << "right reference version, value=" << value << std::endl;
}

template<typename T>
void dispatch_without_forward(T &&t) {
func(t);
}

template<typename T>
void dispatch_with_forward(T &&t) {
func(std::forward<T>(t));
}

int main() {
int value = 0;
dispatch_without_forward(value); // left reference version, value=0
dispatch_without_forward(1); // left reference version, value=1

value = 2;
dispatch_with_forward(value); // left reference version, value=2
dispatch_with_forward(3); // right reference version, value=3

value = 4;
func(std::forward<int>(value)); // right reference version, value=4 (!!! very strange !!!)
value = 5;
func(std::forward<int &>(value)); // left reference version, value=5
func(std::forward<int &&>(6)); // right reference version, value=6
}

标准库的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }

template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}

在使用std::forward时,模板实参都是需要显式指定的,而不是推断出来的

  • 如果模板实参是左值、左值引用或右值引用,那么匹配第一个方法
    • 左值:_Tp&&得到的是个右值(很奇怪吧,因为一般都不是这么用的)
    • 左值引用:_Tp&&得到的是个左值引用(完美转发会用到)
    • 右值应用:_Tp&&得到的是个右值引用(完美转发会用到)
  • 如果模板实参是左值或右值,那么匹配的是第二个方法
    • 右值:_Tp&&得到的是个右值

16 utility

  1. std::pair

17 容器

  1. <vector>
  2. <array>
  3. <list>
  4. <queue>
  5. <deque>
  6. <map>
  7. <unordered_map>
  8. <set>
  9. <unordered_set>

17.1 Tips

  1. std::map或者std::set用下标访问后,即便访问前元素不存在,也会插入一个默认值。因此下标访问是非const
  2. 容器在扩容时,调用的是元素的拷贝构造函数
  3. std::vector<T> v(n)会生成n个对应元素的默认值,而不是起到预留n个元素的空间的作用

18 向量化

Header files for x86 SIMD intrinsics

  1. <mmintrin.h>:MMX
  2. <xmmintrin.h>:SSE
  3. <emmintrin.h>:SSE2
  4. <pmmintrin.h>:SSE3
  5. <tmmintrin.h>:SSSE3
  6. <smmintrin.h>:SSE4.1
  7. <nmmintrin.h>:SSE4.2
  8. <ammintrin.h>:SSE4A
  9. <wmmintrin.h>:AES
  10. <immintrin.h>:AVX, AVX2, FMA
    • 一般用这个即可,包含上述其他的头文件

注意,gccclang默认禁止使用向量化相关的类型以及操作,在使用上述头文件时,需要指定对应的编译参数:

  • -mmmx
  • -msse
  • -msse2
  • -msse3
  • -mssse3
  • -msse4
  • -msse4a
  • -msse4.1
  • -msse4.2
  • -mavx
  • -mavx2
  • -mavx512f
  • -mavx512pf, supports prefetching for gather/scatter, mentioned by Interleaved Multi-Vectorizing
  • -mavx512er
  • -mavx512cd
  • -mavx512vl
  • -mavx512bw
  • -mavx512dq
  • -mavx512ifma
  • -mavx512vbmi

19 C标准库

  1. stdio.h
  2. stddef.h
  3. stdint.h
  4. cstdlib
    • std::atoi
    • std::atol
    • std::atoll