C++新特性学习

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 的单向链接性也意味着它会有一些其他的特性:

  1. 无法使用反向迭代器。只能从它得到const或non-const前向迭代器,这些迭代器都不能解引用,只能自增;
  2. 没有可以返回最后一个元素引用的成员函数back();只有成员函数front();
  3. 因为只能通过自增前面元素的迭代器来到达序列的终点,所以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::variantstd: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

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值