1 容器库
1.1 std::array
定义于头文件 <array>
template<
class T,
std::size_t N
> struct array;
std::array 是std::vector的一个弱化,是封装固定大小数组的容器。在初始化时就必须指定大小,且不会动态增长;其分配的空间是栈上的数组,而不是堆上。
eg1:
std::array<int, 4> arr= {1, 2, 3, 4};
constexpr int len = 4;
std: : array<int, len> arr = {1, 2, 3, 4};
void foo( int *p, int len) {
return;
}
foo(&arr[0] , arr. size() )); // 非法
foo(arr. data(), arr. size());
std::sort(arr.begin() , arr.end());
std::array链接:https://blog.csdn.net/thinkerleo1997/article/details/80415059
1.2 std::forward_list(序列式容器)
设计初衷:“forward_list
设计的目标是, 使用forward_list
不会比使用自己手写的C风格单链表有更多的时间和空间的开销, 任何有悖于这个目标的接口和特性都会被标准拒之门外”
std::forward_list 相当于std::list来说,其实现的为单链表,相当与没进标准的slist。不提供size()方法。
#include <forward_list>
#include <iostream>
using namespace std;
int main()
{
forward_list<int> fl = {1, 2, 3, 4, 5};
for (const auto &elem : fl)
{
cout << elem;
}
return 0;
}
forward_list 容器以单链表的形式存储元素。forward_list 的模板定义在头文件 forward_list 中。fdrward_list 和 list 最主要的区别是:它不能反向遍历元素;只能从头到尾遍历。
forward_list 的单向链接性也意味着它会有一些其他的特性:
- 无法使用反向迭代器。只能从它得到const或non-const前向迭代器,这些迭代器都不能解引用,只能自增;
- 没有可以返回最后一个元素引用的成员函数back();只有成员函数front();
- 因为只能通过自增前面元素的迭代器来到达序列的终点,所以push_back()、pop_back()、emplace_back()也无法使用。
forward_list 的操作比 list 容器还要快,而且占用的内存更少,尽管它在使用上有很多限制,但仅此一点也值得肯定。
forward_list 容器的构造函数的使用方式和 list 容器相同。forward_list 的迭代器都是前向迭代器。它没有成员函数 size(),因此不能用一个前向迭代器减去另一个前向迭代器,但是可以通过使用定义在头文件 iterator 中的 distance() 函数来得到元素的个数。例如:
std::forward_list<std::string> my_words {"three", "six", "eight"};
auto count = std::distance(std::begin(my_words),std::end(my_words));
std::forward_list链接:http://c.biancheng.net/view/448.html
1.3 std::variant
std::variant是类型安全的联合体,是一个加强版的 union,variant支持更加复杂的数据类型,例如map,string等等。c++17标准中引入了variant来作为union的类型安全替代品。它可以在任意时间保存模板参数列表中某一类型的值或者空值。
与union一样,如果某一variant保存类型T的一个值,那么T的对象被直接分配在variant的内部。variant不能在动态内存分配方式中使用。
variant不可存放引用,数组或是void值。空varaint是错误的用法(应该用std::variant<std::monostate>取代)。
与union一样,vairant默认初始化为它模板类型列表中第一个值,除非该项无法默认构造(若默认构造函数无法编译,辅助类std::momostate可用来使variant成为默认可构造的)
variant 的用法如下:
#include <variant>
#include <string>
#include <map>
#include <cassert>
#include <iostream>
int main()
{
std :: variant < int,float > v,w ;
v = 12 ; // v包含int
int i = std :: get < int > (v);
w = std :: get < int > (v);
w = std :: get < 0 >(v); //与前一行相同的效果
w = v ; //与上一行相同的效果
std::map<int ,std::string> st;
st[0] = 20;
st[1] = "https://www.baidu.com";
std::variant <std::string, std::map<int,std::string> > complex;
complex = st;
}
1.4 std::optional
C++17引入了std::optional<T>
,类似于std::variant
,std:optional
是一个和类型(译者注:和类型即sum type,如果你熟悉C++中的union
,那么就不难理解这里的sum。如果一个union
包含两个类型,一个bool
类型和一个uint8_t
类型,那么这个union
一共会有2+2^8 = 258种值,所以我们称之为和类型,因为它们的类型数量是用各个类型的类型数量累加求得的。如果换成struct
,那么这里的类型数量就是2*2^8=512种),它是类型T
的所有值和一个单独的“什么都没有”的状态的和。
后者有专门的名字:它的类型是std::nullopt_t
,并且它有一个值std::nullopt
。那听上去很熟悉,它和nullptr
的概念相同,不同的是后者是C++内置的关键词。
一个常见的用例optional
是可能失败的函数的返回值。与其他方法相反,例如std :: pair < T,bool >,optional
处理昂贵的构造对象并且更具可读性,因为意图明确表达。optional<T>
任何给定时间点的任何实例都包含值或不包含值。
一种常见的 optional 使用情况是一个可能失败的函数的返回值,主要有如下方法
has_value() // 检查对象是否有值
value() // 返回对象的值,值不存在时则抛出 std::bad_optional_access 异常
value_or() // 值存在时返回值,不存在时返回默认值
具体的代码如下:
auto create_string(bool b) {
return b ? std::optional<std::string>{"Godzilla"} :
std::nullopt;
}
auto name = create_string(false);
// 支持bool operator() operator -> operator * 三种操作
if (name) {
printf("%s\t%u\n", name->c_str(), (*name).size())
}
std::optioal链接:https://www.jianshu.com/p/24b4361017f9
1.5 std::any
std::any是一个类型安全的可以保存任何值的容器
1.7 std::string_view
c++17中的string_view 和 boost类似,string_view可以理解成原始字符串一个只读引用。 string_view 本身没有申请额外的内存来存储原始字符串的data,仅仅保存了原始字符串地址和长度等信息。 在很多情况下,我们只是临时处理字符串,本不需要对原始字符串的一份拷贝。
使用string_view可以减少不必要的内存拷贝,可以提高程序性能。相比使用字符串指针,string_view做了更好的封装。
需要注意的是,string_view 由于没有原始字符串的所有权,使用string_view 一定要注意原始字符串的生命周期。
当原始的字符串已经销毁,则不能再调用string_view。
如果需要在C++11中使用string_view 可以通过,如下方法进行移植。
#if __cplusplus >= 201703L
#include <string_view>
using string_view = std::string_view;
#else
#include <boost/string_view>
using string_view = boost::string_view;
#endif
1.8 std::tuple
元组是从pytnon(存疑)中引入的,用于存放不同类型的数据容器,是std::pair的增强版,需要在编译器确定类型。
在C++中是不支持多个返回值的,往往需要用于用户定义一个struct结构体。std::tuple提供了这样的能力。
基本操作有三种
std::make_tuple // 封装元组
std::tie // 解包
std::get<> // 获取其中某些元素
auto get_student( int id) {
std::make_tuple(3. 8, ' A' , "rayan");
}
double gpa;
char grade;
std::string name;
// 通过tie拆包
std::tie(gpa, grade, name) = get_student( 1) ;
std::tie(gpa, std::ignore, name) = get_student(0) ;
// 通过get解元素
auto student = get_student(0) ;
gpa = std::get<0>(student);
grade = std::get<1>(student);
name = std::get<2>(student);
// c++14提供根据type类获取,但tuple中的元素类型需要不一样,否则出发编译错误
grade = std::get<decltype(grade)>(student);
两个tuple可以通过std::tuple_cat进行合并
auto tuple_list = std::tuple_cat(get_student(0), get_student(1));
// 获取tuple的大小
template <typename T>
auto tuple_len( T &tpl) {
return std: : tuple_size<T>: : value;
}
// tuple的遍历 std::get<0>中的0必须是编译期确定
// 所以为了遍历必须有boost::varint的支持,具体代码如下
#include <boost/variant. hpp>
template <size_t n, typename. . . T>
boost::variant<T...> iterative_tuple_index( size_t i, const std::tuple<T... >& tpl) {
if ( i == n)
return std::get<n>( tpl) ;
else if ( n == sizeof...(T) - 1)
throw std::out_of_range( " 越界. ") ;
else
return iterative_tuple_index<( n < sizeof...( T) - 1 ? n+1 : 0) >( i, tpl) ;
}
template <typename...T>
boost::variant<T...> tuple_index( size_t i, const std::tuple<T...>& tpl) {
return iterative_tuple_index<0>( i, tpl) ;
}
// 迭代
for( int i = 0; i ! = tuple_len( new_tuple) ; ++i)
// 运行期索引
std::cout << tuple_index( i, new_tuple) << std::endl;
2 资源管理
2.1 引用计数与智能指针
RAII 资源获取即初始化技术
C++11 引入了智能指针的概念,使用了引用计数的概念,让程序员不需关心手动释放内存。
基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次,每删除一次引用,引用计数就会减一, 当一个对象的引用计数减为零时, 就自动删除指向的堆内存。
这些指针都包含在中
std::shared_ptr|std::unique_ptr|std::weak_ptr.
2.1 shared_ptr
auto pointer = std::shared_ptr<int>(new int(10));
auto pointer = std::make_shared<int>(10); //推荐用法
pointer.reset(); // 减少一次内存引用
pointer.get_count();//查看引用次数
enable_shared_from_this 的原型为
template< class T > class enable_shared_from_this;
主要是为了保活
在异步调用中,存在一个保活机制,异步函数执行的时间点我们是无法确定的,然而异步函数可能会使用到异步调用之前就存在的变量。为了保证该变量在异步函数执期间一直有效,我们可以传递一个指向自身的share_ptr给异步函数,这样在异步函数执行期间share_ptr所管理的对象就不会析构,所使用的变量也会一直有效了(保活)。
2.2 unique_ptr
std::unique_ptr 有些库的实现为std::scoped_ptr,用于替代有问题的auto_ptr,表示独享的智能指针,通过禁止拷贝来禁止与其它智能指针共享同一个对象。
// From C++14
std::unique_ptr<int> pointer = std::make_unique<int>(10);
template<typename T, typename ... Args>
std::unique_ptr<T> make_unique( Args&& ... args ) {
return std::unique_ptr<T>( new T( std::forward<Args>(args)...)) ;
}
std::unique_ptr 还可以自定义释放函数,替代默认的 delete 函数,可以用于模拟golang中的defer语义
#include <stdio.h>
#include <memory>
int main() {
std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("test.txt", "r"), &fclose);
if(fp) {
char buf[4096];
while(fgets(buf, sizeof(buf), fp.get())) {
printf("%s", buf);
}
}
}
对于某些释放函数可以直接用decltype,但需要注意签名需要匹配,如上 decltype(fclose) 是编译不过的。
2.3 weak_ptr
std::weak_ptr 是为了解决两个对象A,B的成员变量相互持有对方的shared_ptr智能指针。
std::weak_ptr 没有 * 运算符和 -> 运算符, 所以不能够对资源进行操作,它的唯一作用就是用 于检查 std::shared_ptr 是否存在, expired()方法在资源未被释放时, 会返回 true, 否则返回false。
TODO 补充示例
3. 随机生成引擎(Random Engine)
stateful generator that generates random value within predefined min and max. Not truely random -- pseudorandom
3.1 default_random_engine
#include <random>
std::default_random_engine eng; //
cout << "Min: " << eng.min() << endl;
cout << "Max: " << eng.max() << endl;
cout << "Max: " << eng() << endl;
std::stringstream state;
state << eng; // Save the state
state >> eng; // Restore the state
// with seed
unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count();
std::default_random_engine e3(seed);
vector<int> d = {1,2,3,4,5,6,7,8,9};
std::shuffle(d.begin(), d.end(), e3);
3.2 uniform_int_distribution
std::uniform_int_distribution<int> distr(0,100); // range: [0~100]
std::default_random_engine eng;
cout << distr(e) << endl;
std::uniform_real_distribution<double> distrReal(0,100); // Range: [0~100)
std::poisson_distribution<int> distrP(1.0); // mean (double)
std::normal_distribution<double> distrN(10.0, 3.0); // mean and standard deviation
4 正则表达式
正则表达式是一种用于在字符串中匹配模式的微型语言。
5 时间相关
相关主要在chrono库,需要#include,其所有实现均在std::chrono namespace下。
chrono是一个模版库,使用简单,功能强大,只需要理解三个概念:duration、time_point、clock
5.1 Durations
std::chrono::duration 表示一段时间。
template <class Rep, class Period = ratio<1> > class duration;
template <intmax_t N, intmax_t D = 1> class ratio;
ratio<3600, 1> hours
ratio<60, 1> minutes
ratio<1, 1> seconds
ratio<1, 1000> microseconds
ratio<1, 1000000> microseconds
ratio<1, 1000000000> nanosecons
由于各种duration表示不同,chrono库提供了duration_cast类型转换函数。
template <class ToDuration, class Rep, class Period>
constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);
如下是一些示例
auto start = std::chrono::high_resolution_clock::now();
//...do something
auto end = std::chrono::high_resolution_clock::now();
typedef std::chrono::microseconds us;
auto diff = std::chrono::duration_cast<us>(end - start);
cout<< diff.count()<<endl;
5.2 time_point
std::chrono::time_point 表示一个具体时间。
template <class Clock, class Duration = typename Clock::duration> class time_point;
5.3 Clock
// system_clock example
#include <iostream>
#include <ctime>
#include <ratio>
#include <chrono>
int main ()
{
using std::chrono::system_clock;
std::chrono::duration<int,std::ratio<60*60*24> > one_day (1);
system_clock::time_point today = system_clock::now();
system_clock::time_point tomorrow = today + one_day;
std::time_t tt;
tt = system_clock::to_time_t ( today );
std::cout << "today is: " << ctime(&tt);
tt = system_clock::to_time_t ( tomorrow );
std::cout << "tomorrow will be: " << ctime(&tt);
return 0;
}
std::chrono::system_clock 它表示当前的系统时钟,系统中运行的所有进程使用now()得到的时间是一致的。
每一个clock类中都有确定的 time_point, duration, Rep, Period类型。
操作有:
now() 当前时间 time_point
to_time_t() time_point 转换成time_t秒
from_time_t() 从time_t转换成time_point
典型的应用是计算时间日期:
6. 内存布局
atomic
7. 同步与信号量
C++11的一个特色是将多线程标准化了,在引入的时候需要链接 -lpthread,否则会报错。
6.1 mutex
mutex 类是能用于保护共享数据免受从多个线程同时访问的同步原语。在Linux下的实现为 pthread_mutex_xxx 系列函数。
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
一般情形我们不直接用其lock和unlock 接口,跟 std::lock_guard 配合,以免忘记释放unlock 导致死锁问题。
#include <thread>
#include <mutex>
std::mutex g_pages_mutex;
int value_;
unsigned int get() const {
std::lock_guard<std::mutex> guard(g_pages_mutex);
return ++value_;
}
此外,还有带时间的互斥锁 timed_mutex 基本都是 pthread 函数的参数封装而已,在此不再举例。
6.2 shared_mutex
shared_mutex 类是一个同步原语,可用于保护共享数据不被多个线程同时访问。与便于独占访问的其他互斥类型不同,本质上是pthread标准库中的读写锁 linux_pthread_rwlock 的封装。
int pthread_rwlock_init(pthread_rwlock_t * restrict lock,
const pthread_rwlockattr_t * restrict attr);
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
int pthread_rwlock_destroy(pthread_rwlock_t *lock);
//读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *lock);
int pthread_rwlock_timedrdlock(pthread_rwlock_t * restrict lock,
const struct timespec * restrict abstime);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *lock);
// 写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *lock);
int pthread_rwlock_timedwrlock(pthread_rwlock_t * restrict lock,
const struct timespec * restrict abstime);
int pthread_rwlock_trywrlock(pthread_rwlock_t *lock);
// 释放锁
int pthread_rwlock_unlock(pthread_rwlock_t *lock);
一般情况下我们不直接使用其lock和unlock 接口,而通过 读锁 shared_lock 和 写锁 unique_lock 来访问,示例如下
此外还有 带时间参数的 shared_timed_mutex 可以在有需求的时候使用。
6.3 condition_variable
condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable。定义在头文件中,对应于pthread API 的 pthread_cond_XXX系列函数。
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);//创建一个 condition的内核变量
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//等待内核变量变为signaled
//在指定时间内等待内核心变量变为signaled
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);//“析构”一个condition的内核变量
//把内核变量置为signaled,指定一个等待线程激活,但不能指定特定线程
int pthread_cond_signal(pthread_cond_t *cond);
//把内核变量置为signaled,指定所有等待线程激活
int pthread_cond_broadcast(pthread_cond_t *cond);
一般条件变量用于生产者和消费者模型,具体示例如下
std::condition_variable_any
与 std::condition_variable 类似,只不过 std::condition_variable_any 的 wait 函数可以接受任何 lockable 参数,而 std::condition_variable 只能接受 std::unique_lockstd::mutex 类型的参数。
6.4 semaphore
在标准库中,并没有实现 semaphore,但是我们可以通过如上的 mutex和 condition_variable 进行模拟一下,具体的实现如下
#pragma once
#include <condition_variable>
#include <mutex>
class Semaphore {
public:
explicit Semaphore(int count = 0) : count_(count) {}
void Signal() {
std::unique_lock<std::mutex> lock(mutex_);
++count_;
cv_.notify_one();
}
void Wait() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [=] { return count_ > 0; });
--count_;
}
private:
std::mutex mutex_;
std::condition_variable cv_;
int count_;
};
6.5 移植性
因为C++11并没有完成所有的封装,有些甚至到C++17才添加到标准库中,所以在低版本的时候,需要用boost的相关lib进行移植,
下面是一个参考的示例。
#if __cplusplus < 201703L
#include <boost/optional.hpp>
#include <boost/thread.hpp>
template <typename T>
using optional = boost::optional<T>;
using shared_mutex = boost::shared_mutex;
using shared_lock = boost::shared_lock<shared_mutex>;
using unique_lock = boost::unique_lock<shared_mutex>;
#else
#include <optional>
#include <shared_mutex>
#include <mutex> //for unique_lock
#include <memory>
template <typename T>
using optional = std::optional<T>;
using shared_mutex = std::shared_mutex;
using shared_lock = std::shared_lock<shared_mutex>;
using unique_lock = std::unique_lock<shared_mutex>;
#endif