*★,°*:.☆( ̄▽ ̄)/$:*.°★* 欢迎来到汤姆的csdn博文 喜欢的朋友可以关注一下,下次更新不迷路 私聊获取个人订阅号哦,欢迎订阅共同学习 可以加入大家庭群聊,一起学习天天有福利
对于想要成为一名资深C++工程师的我来说,对于C++历代版本更新的了解是不可或缺的,每一个版本增加的新特性都是C++这门语言进化的成果。如果你问我为什么喜欢C++这门语言,我可以告诉你的是,C++这门语言它虽然不完美,有很多让人诟病的缺点,但是它一直在成长,一直在进化,一直在变得越来越好,在保持自己的优点的前提下努力的学习其它语言的优点,争取让自己变得越来越好,就好像一个刚刚踏入编程生涯的小白程序员,只有通过不断地学习才能一点一点逐渐的成长,变得更加的健壮,所以,我爱C++因为它如此的真实。这篇文章我会花大量的时间和巨长的篇幅带来一份尽可能详尽的系统的C++历代版本的演变和新特性的介绍,希望能够让读者读完后有所收获,欢迎点赞评论收藏加订阅呀。
年份 | C++标准 | 通用名 |
---|---|---|
1978 | C with Classes | - |
1998 | ISO/IEC 14882:1998 | C++98 |
2003 | ISO/IEC 14882:2003 | C++03 |
2011 | ISO/IEC 14882:2011 | C++11 |
2014 | ISO/IEC 14882:2014 | C++14 |
2017 | ISO/IEC 14882:2017 | C++17 |
2020 | ISO/IEC 14882:2020 | C++20 |
2023 | waiting | waiting |
小知识
什么是C++0X:上一个版本的C++国际标准是2003年发布的,所以叫C++ 03。然后C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是07年发布,所以最初这个标准叫C++ 07。但是到06年的时候,官方觉得07年肯定完不成C++ 07,而且官方觉得08年可能也完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11。
小知识
C++的简单由来:
1998年是C++标准委员会成立的第一年,以后每5年视实际需要更新一次标准。
2009年,C++标准有了一次更新,一般称该草案为C++0x。
C++0x是C++11标准成为正式标准之前的草案临时名字。
后来,2011年,C++新标准标准正式通过,更名为ISO/IEC 14882:2011,简称C++11。
小知识
C++11后的进化史:
- C++11,先前被称作C++0x,即ISO/IEC 14882:2011,是C++编程语言的一个标准。
它取代第二版标准ISO/IEC 14882:2003 (第一版ISO/IEC 14882:1998公开于1998年, 第二版于2003年更新,分别通称C++98以及C++03,两者差异很小),且已被C++14取代- C++14 旨在作为C++11的一个小扩展,主要提供漏洞修复和小的改进。
2014年8月18日,经过C++标准委员投票,C++14标准获得一致通过。ISO/IEC 14882:2014- C++17 又称C++1z,是继 C++14 之后,C++ 编程语言 ISO/IEC 标准的下一次修订的非正式名称。
官方名称 ISO/IEC 14882:2017,基于 C++ 11,C++ 17 旨在简化该语言的日常使用,使开发者可以更简单地编写和维护代码。
-C++20在2020年更新,用C++之父的话说C++20目的就是stability–稳定(稳定压到一切)和
evolution–更新(与时俱进,走到潮流之巅)
#include "iostream"
#include "vector"
auto func() { //函数的返回值可以用auto自动推导
auto b = std::vector<int>{1, 2, 3, 4};
for (auto i: b) {//auto还可以这么用
std::cout << i << std::endl;
}
}
int main() {
auto a = 10;//无需显式的声明int a=10,编译器在运行的时候会自动推导。
return 0;
};
注意事项:
int main() {
auto a=0;
auto b;//不进行初始化会报错
};
int main() {
auto a=10,b=10.0;//一个是int一个是非int类型,无法正确推导
};
#include "iostream"
#include "vector"
int main() {
int a = 10;
int &b = a;
auto c = b;
auto &d = b;
c = 100;
std::cout << a << std::endl;//输出为100
d = 1000;
std::cout << a << std::endl;//输出为1000
std::cout << typeid(c).name() << std::endl;//输出为int
std::cout << typeid(d).name() << std::endl;//输出为int
std::cout << &a << std::endl;//输出为0000003225CFFCB4
std::cout << &b << std::endl;//输出为0000003225CFFCB4
std::cout << &c << std::endl;//输出为0000003225CFFCD4
std::cout << &d << std::endl;//输出为0000003225CFFCB4
};
#include "iostream"
#include "vector"
int main() {
const int a1 = 10;
auto b1= a1; //b1的类型为int而非const int(去除const)
const auto c1 = a1;//此时c1的类型为const int
b1 = 100;//合法
c1 = 100;//非法
};
#include "iostream"
#include "vector"
int main() {
const int a2 = 10;
auto &b2 = a2;//因为auto带上&,故不去除const,b2类型为const int
std::cout<< typeid(a2).name()<<std::endl;//int 为const int
std::cout<< typeid(b2).name()<<std::endl;//int 为const int
b2=1;//非法
};
#include "iostream"
#include "vector"
int main() {
int a[3] = {1, 2, 3};
auto b = a;
auto &c = a;
std::cout << typeid(a).name() << std::endl;//int [3]
std::cout << typeid(b).name() << std::endl;//int *__ptr64
std::cout << typeid(c).name() << std::endl;//int [3]
};
void func(auto a) //错误
{
//...
}
cout << sizeof(auto) << endl;//错误
cout << typeid(auto).name() << endl;//错误
decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算,decltype不会像auto一样忽略引用和cv属性,decltype会保留表达式的引用和cv属性
对于decltype(exp)有
#include "iostream"
#include "vector"
#include "functional"
void *fun() {
return nullptr;
}
int main() {
int a = 3;
decltype(a) b = 0;
decltype(fun()) c;//
decltype(a + b) d = 0;//d是int,因为(a+b)返回一个右值
decltype(a += b) e = d;//e是int&,因为(a+=b)返回一个左值
std::cout << typeid(a).name() << std::endl;//int
std::cout << typeid(b).name() << std::endl;//int
std::cout << typeid(c).name() << std::endl;//void * __ptr64
std::cout << typeid(d).name() << std::endl;//int
std::cout << typeid(e).name() << std::endl;//int
std::cout << d << std::endl;//0
e = 5;
std::cout << d << std::endl;//5 因为e是int&
};
小知识
decltype如何配合auto使用
auto和decltype一般配合使用在推导函数返回值的类型问题上,
例如
template
decltype(t + u) add(T t, U u) { // t和u尚未定义 return t + u; }
这段代码在C++11上是编译不过的,因为在decltype(t +u)推导时,t和u尚未定义,就会编译出错,所以有了下面的叫做返回类型后置的配合使用方法:
template
auto add(T t, U u) -> decltype(t + u) { return t + u; }
返回值后置类型语法就是为了解决函数返回制类型依赖于参数但却难以确定返回值类型的问题。
编译器自动判断了返回值类型,牛啊!
这个知识点请跳转我另一篇文章,讲的非常的清楚明白
2023-3-9-一篇简短的文章把C++左右值关系讲的透透彻彻
C++11新增了列表初始化的概念。
在C++11中可以直接在变量名后面加上初始化列表来进行对象的初始化。
#include "iostream"
#include "vector"
#include "functional"
int main() {
std::vector a {1, 2, 3, 4, 5, 6};
};
列表初始化的一些规则:
首先说下聚合类型可以进行直接列表初始化,这里需要了解什么是聚合类型:
#include "iostream"
#include "vector"
#include "functional"
class A{
public:
int a;//如果为private则错误
};
int main() {
A a{0};
};
struct A {
int a;
int b;
int c;
A(int, int){}
};
int main() {
A a{1, 2, 3};// error,A有自定义的构造函数,不能列表初始化
}
struct A {
int a;
int b;
virtual void func() {} // 含有虚函数,不是聚合类
};
struct Base {};
struct B : public Base { // 有基类,不是聚合类
int a;
int b;
};
struct C {
int a;
int b = 10; // 有等号初始化,不是聚合类
};
struct D {
int a;
int b;
private:
int c; // 含有私有的非静态数据成员,不是聚合类
};
struct E {
int a;
int b;
E() : a(0), b(0) {} // 含有默认成员初始化器,不是聚合类
};
上面列举了一些不是聚合类的例子,对于一个聚合类型,使用列表初始化相当于对其中的每个元素分别赋值;对于非聚合类型,需要先自定义一个对应的构造函数,此时列表初始化将调用相应的构造函数。
小知识
我们平时开发使用STL过程中可能发现它的初始化列表可以是任意长度,大家有没有想过它是怎么实现的呢,答案是std::initializer_list,看下面这段示例代码:
struct CustomVec { std::vector
data; CustomVec(std::initializer_list list) { for (auto iter = list.begin(); iter != list.end(); ++iter) { data.push_back(*iter); } } };
std::initializer_list,它可以接收任意长度的初始化列表,但是里面必须是相同类型T,或者都可以转换为T。
std::function是可调用对象的封装器,可以把std::function看做一个函数对象,用于表示函数这个抽象概念。std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标,若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常。
小知识
什么是可调用对象
满足以下条件之一就可称为可调用对象:
是一个函数指针
是一个具有operator()成员函数的类对象(传说中的仿函数),lambda表达式
是一个可被转换为函数指针的类对象
是一个类成员(函数)指针
bind表达式或其它函数对象
#include "iostream"
#include "vector"
#include "functional"
class A {
public:
A(int a) {}
void print_a() {
std::cout << a << std::endl;
}
public:
int a = 4;
};
void print_num(int i) {
std::cout << i << std::endl;
}
int main() {
//存储自由函数
std::function<void(int)> function = print_num;
function(1);//输出1
//存储lambda
std::function<void(int)> function1 = [](int i) {
std::cout << i << std::endl;
};
function1(2);//输出2
//存储到 std::bind 调用的结果
std::function < void() > function2 = std::bind(print_num, 3);
function2();//输出3 如果function2为void(int)类型,那么该函数传的参数不会影响结果,实际的参数是bind绑定的参数
//存储到成员函数的调用
std::function<void(A)> function3 = &A::print_a;
function3(A{4});//输出4
};
当给std::function填入合适的参数表和返回值后,它就变成了可以容纳所有这一类调用方式的函数封装器。std::function还可以用作回调函数,或者在C++里如果需要使用回调那就一定要使用std::function,特别方便。
使用std::bind可以将可调用对象和参数一起绑定,最主要的功能就是实现延迟调用
#include "iostream"
#include "vector"
#include "functional"
#include
void print_num(int i,int k,int j) {
std::cout << i << std::endl;
}
int main() {
//存储自由函数
std::function<void(int,int)> function = std::bind(print_num,5,std::placeholders::_1,std::placeholders::_2);
function(2,3);//输出1
};
lambda表达式可以说是c++11引用的最重要的特性之一,它定义了一个匿名函数,可以捕获一定范围的变量在函数内部使用,一般有如下语法形式:
auto func = [capture] (params) opt -> ret { func_body; };
其中func是可以当作lambda表达式的名字,作为一个函数使用,capture是捕获列表,params是参数表,opt是函数选项(mutable之类), ret是返回值类型,func_body是函数体。
一个完整的lambda表达式:
auto func1 = [](int a) -> int { return a + 1; };
auto func2 = [](int a) { return a + 2; };
cout << func1(1) << " " << func2(2) << endl;
如上代码,很多时候lambda表达式返回值是很明显的,c++11允许省略表达式的返回值定义。
lambda表达式允许捕获一定范围内的变量:
#include "iostream"
#include "vector"
#include "functional"
int main() {
//值捕获
int a = 0;
std::function < void() > function = [=]() -> void {
a=10;//报错
std::cout << a << std::endl;//0
};
function();
std::cout << a << std::endl;//0
//引用捕获
std::function < void() > function1 = [&]() -> void {
a=10;
std::cout << a << std::endl;//10
};
function1();
std::cout << a << std::endl;//10
};
代码中的a=10是编译不过的,因为我们修改了按值捕获的外部变量,其实lambda表达式就相当于是一个仿函数,仿函数是一个有operator()成员函数的类对象,这个operator()默认是const的,所以不能修改成员变量,而加了mutable,就是去掉const属性。
#include "iostream"
#include "vector"
#include "functional"
int main() {
//值捕获加mutable
int a = 0;
std::function < void() > function = [=]() mutable -> void {
a=10;//此时不报错
std::cout << a << std::endl;//10
};
function();
std::cout << a << std::endl;//0
};
模板的右尖括号
C++11之前是不允许两个右尖括号出现的,会被认为是右移操作符,所以需要中间加个空格进行分割,避免发生编译错误。
模板的别名
C++11引入了using,可以轻松的定义别名,而不是使用繁琐的typedef。
typedef std::vector<std::vector<int>> vvi; // before c++11
using vvi = std::vector<std::vector<int>>; // c++11
函数模板的默认模板参数
C++11之前只有类模板支持默认模板参数,函数模板是不支持默认模板参数的,C++11后都支持。
c++11引入了std::thread来创建线程,支持对线程join或者detach
#include "iostream"
#include "thread"
#include "memory"
#include "functional"
int main() {
std::function < void() > func = []() {
for (int i = 0; i < 10; i++) {
std::cout << i << " ";
}
std::cout << std::endl;
};
std::thread t(func);
if(t.joinable())
{
t.detach();//使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束,可能会在后台监视文件系统,还有可能对缓存进行清理,亦或对数据结构进行优化。另一方面,分离线程的另一方面只能确定线程什么时候结束,发后即忘(fire andforget)的任务就使用到线程的这种方式
}
std::function < void(int) > func1 = [](int k) {
for (int i = 0; i < k; i++) {
std::cout << i << " ";
}
std::cout << std::endl;
};
std::thread tt(func1, 20);
if(tt.joinable())
{
tt.join();//join()函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束
}
return 0;
}
std::mutex是一种线程同步的手段,用于保存多线程同时操作的共享数据。
mutex分为四种:
#include "iostream"
#include "thread"
#include "mutex"
#include "functional"
std::timed_mutex mutex;
int main() {
std::function < void(int) > func1 = [](int k) {
mutex.try_lock_for(std::chrono::microseconds (1));
for (int i = 0; i < k; i++) {
std::cout << i << " ";
}
std::cout << std::endl;
mutex.unlock();
};
std::vector<std::thread> threads;
for(int i=0;i<5;i++)
{
threads.emplace_back(std::thread(func1,200));
}
for(auto& i:threads)
{
if(i.joinable())
{
i.join();
}
}
return 0;
}
//因为只加了一微秒的锁,锁一解开就开始出现线程抢占资源的现象
//0 1 2 3 4 5 6 7 8 9 10 11 120 13 14 115 216 3 17 4 518 6 19 7 20 21 228 923 24 25 26 27 28 29 30 31 32 33 34 35 36 370 1 380 39 1 2 3 4 510 60 11140 7 122 2 3 48 9 5 31013 11 14 1512 441 426 743 8 44 5
//4513 14 15 4616 17 47 18 1948 20 4921 50 51 52 22 23 24916 10 1117 18 19 20 2125 22 626 7 27 8 912 10 53 54 5523 24 2528 26 272911 3013 12 1413 15 14 1656 17 57 5831 1815 19 20 2128 3259 221629 17 30 18 31 1933 20 2334 3235 33 60 36 3461 213524 36 25 37 38 26 39 27 40 284122 29 30 31 3237 33 34 35 36 3742 3823 39 2462 25 63 26 6443 38 65 44 4566 46 674039 41 4047 48 49 50 5168 52 69 5327 54 28 55 2942 30 43 44 31 32 334541 4670 4742 48 49 5034 51 35 3656 37 5738 5839 5971 60 6172 62 7343 44 45 4046 41 47 42 4348 7452 75 5376 77 54 55 63 6444 4549 5056 65 57 66 58 5946 4751 48 52 4978 50 5179 526067 61 68 6953 70 5362 54 55 56 5463 5580 56 5781 5782 58 597158 72 7364 74 75 65 7683 77 60 7866 79 6784 68 59 69 60 70 61 71 72 6261 63 646285 6380 64 73 7465 75 8681 82 876683 88 8984 7667 77 65 78 90 79 80 8166 8285 83 84 85 86 8786 88 8991 90 91 92 6892 69 937093 94 9567 876896 69 8870 89 90 91 92 93 9471 95 72 9671 9472 95 73 7497 9798 9899 9973100 101 100102 101 103 104 105102 106 103 107 75108 109 110 11176 112 113 7774 114 115104 116 117105 78106118 107 119 10896 97 9879 9912075 12180 122 123 12481 12576 12677 127 12882 129 78 13079 131100 132 83 133 84101 85 10286 87109 110 111 112 113103 10480 105 106 81107 108134 109 135 11482 115 83 13688 137 138 13989 140 141 14284 143 144110 145 146 111147116 148 85 8690 87 88 8991 11290 113117 114 115 116 1179192 92 93149 94 95 150 96 151118118 97 93119 11994 120 98120 121 99152 10015395 154 155 15696 121 122 123 124 125 157101 122 102 12310397 104 98 105 126106 127 107 108124158 125 159126 99160 100128 101 102 103 104129 130105 131 161132 162 109133 110 111106 127163 128 164 165 166129 167134 168112 113135 114 136 137115 116 117 107 169138 170 171 172118 173130 174 131 132139 133 140 134 119175 120 176 177 178121 179 122 180 123 181141 182 183108 109 110 184142 185 186 143124 144 125111 126 112 113 127145 128187 146 188129147 189 148 190114 191 192135 193130 194 131 195149 132 133196 134 135136115 137 138150 151 152 153 154 155 156 197 198139116 140 141 142117 143 118 119157 158 159 199136 137
// 120160 121 161 162 122144 138 145 139 123 146 163124 164 125 126 165 //140147166 127148 128 149 129 130167 131 150132 133151 134 152 135 153 136 137 154141 168142 169 143 170 144 155 138 156 139171 140 141145 142 143146 144 147157 172 145 158 146148 149159 150 151 160 173161 162 163174 164 165 166 167 152 153175168 169 170 147171 148154 149172 155176 156 157177 178 150179 180 151 158152 153 159154 160173 174181 175161 162176 177 178 155163 156 164 157165179 166182 167 168 169158 159183 160 161 184180 185 186170 171162 172 173163181 164 165182 183 166 174187 175 176 177167 168178188 169 179184 189 185 190 186170 180191 181171 182 187172 188 192173 183 193 184 194 185 195 186 189196174 190 197 187175198 188 199 189191 190 176
//191 192192 193 193194 177195 178 194179 195 180 196196 197 198 181199 182
// 183197 184198 185199 186
// 187 188 189 190 191 192 193 194 195 196 197 198 199
std::lock相关
可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁,c++11主要有std::lock_guard和std::unique_lock两种方式,使用方式都类似。
小知识
lock_guard 类是一个mutex封装者,它为了拥有一个或多个mutex而提供了一种方便的 RAII style 机制。( 译注:所谓的RAII,全称为Resource Acquisition Is Initialization,汉语是“资源获取即初始化”。但是这个直译并没有很好地解释这个词组的含义。其实含义并不高深复杂,简单说来就是,在资源获取的时候将其封装在某类的object中,利用"栈资源会在相应object的生命周期结束时自动销毁"来自动释放资源,即,将资源释放写在析构函数中。所以这个RAII其实就是和智能指针的实现是类似的。)。
当一个lock_guard对象被创建后,它就会尝试去获得给到它的mutex的所有权。当控制权不在该lock_guard对象所被创建的那个范围后,该lock_guard就会被析构,从而mutex被释放。
定义lock_guard的时候调用构造函数加锁,大括号解锁的时候调用析构函数解锁。虽然lock_guard挺好用的,但是有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的粒度就很大,很大程序上会影响效率。因此就引入了unique_lock
小知识
unique_lock<> unique();
这个会在构造函数加锁,然后可以利用unique.unlock()来解锁,所以当你觉得锁的粒度太多的时候,可以利用这个来解锁,而析构的时候会判断当前锁的状态来决定是否解锁,如果当前状态已经是解锁状态了,那么就不会再次解锁,而如果当前状态是加锁状态,就会自动调用unique.unlock()来解锁。而lock_guard在析构的时候一定会解锁,也没有中途解锁的功能。当然,方便肯定是有代价的,unique_lock内部会维护一个锁的状态,所以在效率上肯定会比lock_guard慢。
#include "iostream"
#include "thread"
#include "mutex"
#include "functional"
std::timed_mutex mutex;
int main() {
std::function < void(int) > func1 = [](int k) {
std::lock_guard<std::timed_mutex> lock(mutex);
for (int i = 0; i < k; i++) {
std::cout << i << " ";
}
lock.unlock();//报错,不支持中途解锁,会导致锁的粒度太大,可以使用unique_lock
std::cout << std::endl;
};
return 0;
}
std::atomic相关
c++11提供了原子类型std::atomic,理论上这个T可以是任意类型
#include "iostream"
#include "thread"
#include "mutex"
#include "functional"
struct NewCounter { // 使用原子变量的计数器
std::atomic<int> count;
void add() {
++count;
// count.store(++count);这种方式也可以
}
void sub() {
--count;
// count.store(--count);
}
int get() {
return count.load();
}
};
int main() {
return 0;
}
std::call_once相关
c++11提供了std::call_once来保证某一函数在多线程环境中只调用一次,它需要配合std::once_flag使用
#include "iostream"
#include "thread"
#include "mutex"
#include "functional"
std::once_flag onceFlag;
int main() {
std::function<void()> func1 = []() {
std::call_once(onceFlag, []() {
std::cout << "call once" << std::endl;
});
};
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(func1);
}
for (auto &th: threads) {
th.join();
}
return 0;
}
std::condition_variable相关
条件变量是c++11引入的一种同步机制,它可以阻塞一个线程或者个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程,条件变量需要和锁配合使用,这里的锁就是上面介绍的std::unique_lock。
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // std::condition_variable
std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.
void do_print_id(int id) {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{ return ready; });//一定要这么写,用while还是会出现虚假唤醒,不知道为啥
// 线程被唤醒, 继续往下执行打印线程编号id.
std::cout << "thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lck(mtx);
ready = true; // 设置全局标志位为 true.
cv.notify_one(); // 唤醒所有线程.
}
void vecthread() {
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(do_print_id, i);
for (auto &th: threads) th.detach();
}
int main() {
std::thread create_vecthread(vecthread);
create_vecthread.join(); //因为join了以后必定先执行
std::cout << "10 threads ready to race...\n";
go(); // go!
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
std::future相关
c++11关于异步操作提供了future相关的类,主要有std::future、std::promise和std::packaged_task,std::future比std::thread高级些,std::future作为异步结果的传输通道,通过get()可以很方便的获取线程函数的返回值,std::promise用来包装一个值,将数据和future绑定起来,而std::packaged_task则用来包装一个调用对象,将函数和future绑定起来,方便异步调用。而std::future是不可以复制的,如果需要复制放到容器中可以使用std::shared_future。
小知识
三者之间的关系
std::future用于访问异步操作的结果,而std::promise和std::packaged_task在future高一层,它们内部都有一个future,promise包装的是一个值,packaged_task包装的是一个函数,当需要获取线程中的某个值,可以使用std::promise,当需要获取线程函数返回值,可以使用std::packaged_task。
#include
#include
#include
#include // std::promise, std::future
void print_int(std::future<int>& fut) {
int x = fut.get(); // 获取共享状态的值.
std::cout << "value: " << x << '\n'; // 打印 value: 10.
}
int main ()
{
std::promise<int> prom; // 生成一个 std::promise 对象.
std::future<int> fut = prom.get_future(); // 和 future 关联.
std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.
prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.
t.join();
return 0;
}
#include
#include
#include
#include
using namespace std;
int func(int in) {
return in + 1;
}
int main() {
std::packaged_task<int(int)> task(func);
std::future<int> fut = task.get_future();
std::thread(std::move(task), 5).detach();
cout << "result " << fut.get() << endl;
return 0;
}
async相关
async是比future,packaged_task,promise更高级的东西,它是基于任务的异步操作,通过async可以直接创建异步的任务,返回的结果会保存在future中,不需要像packaged_task和promise那么麻烦,关于线程操作应该优先使用async
async具体语法如下:
async(std::launch::async | std::launch::deferred, func, args...);
功能:第二个参数接收一个可调用对象(仿函数、lambda表达式、类成员函数、普通函数…)作为参数,并且异步或是同步执行他们。
对于是异步执行还是同步执行,由第一个参数的执行策略决定:
(1)、std::launch::async 传递的可调用对象异步执行;
(2)、std::launch::deferred 传递的可调用对象同步执行;
(3)、std::launch::async | std::launch::deferred 可以异步或是同步,取决于操作系统,我们无法控制;
(4)、如果我们不指定策略,则相当于(3)。
对于执行结果:
我们可以使用get、wait、wait_for、wait_until等待执行结束,区别是get可以获得执行的结果。如果选择异步执行策略,调用get时,如果异步执行没有结束,get会阻塞当前调用线程,直到异步执行结束并获得结果,如果异步执行已经结束,不等待获取执行结果;如果选择同步执行策略,只有当调用get函数时,同步调用才真正执行,这也被称为函数调用被延迟。
#include
#include
#include
#include
#include "time.h"
#include "chrono"
using namespace std;
int func(int in) {
this_thread::sleep_for(chrono::microseconds(1000));
return in + 1;
}
int main() {
std::future<int> res = std::async(func, 5);
std::future_status status;
do {
status = res.wait_for(std::chrono::microseconds(0));
switch (status)
{
case std::future_status::ready:
std::cout << "Ready..." << std::endl;
//获取结果
std::cout << res.get() << std::endl;
break;
case std::future_status::timeout:
std::cout << "timeout..." << std::endl;
break;
case std::future_status::deferred:
std::cout << "deferred..." << std::endl;
break;
default:
break;
}
} while (status != std::future_status::ready);
cout << res.get() << endl; // 这里会报错,使用std::shared_future不会报错
return 0;
}
std::future与std::shard_future的用途都是为了占位,但是两者有些许差别。std::future的get()成员函数是转移数据所有权;std::shared_future的get()成员函数是复制数据。 因此: future对象的get()只能调用一次;无法实现多个线程等待同一个异步线程,一旦其中一个线程获取了异步线程的返回值,其他线程就无法再次获取。 std::shared_future对象的get()可以调用多次;可以实现多个线程等待同一个异步线程,每个线程都可以获取异步线程的返回值。
c++11引入了三种智能指针:
shared_ptr
shared_ptr使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。
智能指针还可以自定义删除器,在引用计数为0的时候自动调用删除器来释放对象的内存,代码如下:
std::shared_ptr<int> ptr(new int, [](int *p){ delete p; });
weak_ptr
weak_ptr是用来监视shared_ptr的生命周期,它不管理shared_ptr内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。
unique_ptr
std::unique_ptr是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr的拷贝和赋值。使用方法和shared_ptr类似,区别是不可以拷贝,unique_ptr也可以像shared_ptr一样自定义删除器,使用方法和shared_ptr相同。
#include
#include
#include "chrono"
using namespace std;
class A {
public:
A() {};
~A() {
std::cout << "111" << std::endl;
}
};
int main() {
std::shared_ptr<A> sharedPtr = std::make_shared<A>();
std::unique_ptr<A> uniquePtr = std::unique_ptr<A>(new A());
std::shared_ptr<A> sharedPtr1(new A(), [](A *a) {
std::cout << "wo" << std::endl;
});//自定义删除器
//输出wo 111 111 后构造的先析构
return 0;
}
vector<int> vec;
for (auto iter = vec.begin(); iter != vec.end(); iter++) { // before c++11
cout << *iter << endl;
}
for (int i : vec) { // c++11基于范围的for循环
cout << "i" << endl;
}
委托构造函数允许在同一个类中一个构造函数调用另外一个构造函数,可以在变量初始化时简化操作,通过代码来感受下委托构造函数的妙处吧:
struct A {
A(){}
A(int a) { a_ = a; }
A(int a, int b) : A(a) { b_ = b; }
A(int a, int b, int c) : A(a, b) { c_ = c; }
int a_;
int b_;
int c_;
};
继承构造函数可以让派生类直接使用基类的构造函数,如果有一个派生类,我们希望派生类采用和基类一样的构造方式,可以直接使用基类的构造函数,而不是再重新写一遍构造函数,老规矩,看代码:
不使用继承构造函数:
struct Base {
Base() {}
Base(int a) { a_ = a; }
Base(int a, int b) : Base(a) { b_ = b; }
Base(int a, int b, int c) : Base(a, b) { c_ = c; }
int a_;
int b_;
int c_;
};
struct Derived : Base {
Derived() {}
Derived(int a) : Base(a) {} // 好麻烦
Derived(int a, int b) : Base(a, b) {} // 好麻烦
Derived(int a, int b, int c) : Base(a, b, c) {} // 好麻烦
};
int main() {
Derived a(1, 2, 3);
return 0;
}
使用继承构造函数:
struct Base {
Base() {}
Base(int a) { a_ = a; }
Base(int a, int b) : Base(a) { b_ = b; }
Base(int a, int b, int c) : Base(a, b) { c_ = c; }
int a_;
int b_;
int c_;
};
struct Derived : Base {
using Base::Base;
};
int main() {
Derived a(1, 2, 3);
return 0;
}
只需要使用using Base::Base继承构造函数,就免去了很多重写代码的麻烦。
nullptr是c++11用来表示空指针新引入的常量值,在c++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是个int型的0,其实不是个指针。举例:
void func(void *ptr) {
cout << "func ptr" << endl;
}
void func(int i) {
cout << "func i" << endl;
}
int main() {
func(NULL); // 编译失败,会产生二义性
func(nullptr); // 输出func ptr
return 0;
}
final用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载。
struct Base final {
virtual void func() {
cout << "base" << endl;
}
};
struct Derived : public Base{ // 编译失败,final修饰的类不可以被继承
void func() override {
cout << "derived" << endl;
}
};
override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误。
struct Base {
virtual void func() {
cout << "base" << endl;
}
};
struct Derived : public Base{
void func() override { // 确保func被重写
cout << "derived" << endl;
}
void fu() override { // error,基类没有fu(),不可以被重写
}
};
c++11引入default特性,多数时候用于声明构造函数为默认构造函数,如果类中有了自定义的构造函数,编译器就不会隐式生成默认构造函数,如下代码:
struct A {
int a;
A(int i) { a = i; }
};
int main() {
A a; // 编译出错
return 0;
}
上面代码编译出错,因为没有匹配的构造函数,因为编译器没有生成默认构造函数,而通过default,程序员只需在函数声明后加上“=default;”,就可将该函数声明为 defaulted 函数,编译器将为显式声明的 defaulted 函数自动生成函数体,如下:
struct A {
A() = default;
int a;
A(int i) { a = i; }
};
int main() {
A a;
return 0;
}
编译通过。
c++中,如果开发人员没有定义特殊成员函数,那么编译器在需要特殊成员函数时候会隐式自动生成一个默认的特殊成员函数,例如拷贝构造函数或者拷贝赋值操作符,如下代码:
struct A {
A() = default;
int a;
A(int i) { a = i; }
};
int main() {
A a1;
A a2 = a1; // 正确,调用编译器隐式生成的默认拷贝构造函数
A a3;
a3 = a1; // 正确,调用编译器隐式生成的默认拷贝赋值操作符
}
而我们有时候想禁止对象的拷贝与赋值,可以使用delete修饰,如下:
struct A {
A() = default;
A(const A&) = delete;
A& operator=(const A&) = delete;
int a;
A(int i) { a = i; }
};
int main() {
A a1;
A a2 = a1; // 错误,拷贝构造函数被禁用
A a3;
a3 = a1; // 错误,拷贝赋值操作符被禁用
}
delele函数在c++11中很常用,std::unique_ptr就是通过delete修饰来禁止对象的拷贝的。
explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换,根据代码看explicit的作用:
#include
using namespace std;
class Point {
public:
int x, y;
Point(int x = 0, int y = 0)
: x(x), y(y) {}
};
void displayPoint(const Point& p)
{
cout << "(" << p.x << ","
<< p.y << ")" << endl;
}
int main()
{
displayPoint(1);//输出(1,0),y使用了默认值0
Point p = 1;
}
我们定义了一个再简单不过的Point类, 它的构造函数使用了默认参数. 这时主函数里的两句话都会触发该构造函数的隐式调用. (如果构造函数不使用默认参数, 会在编译时报错)
显然, 函数displayPoint需要的是Point类型的参数, 而我们传入的是一个int, 这个程序却能成功运行, 就是因为这隐式调用. 另外说一句, 在对象刚刚定义时, 即使你使用的是赋值操作符=, 也是会调用构造函数, 而不是重载的operator=运算符.
这样悄悄发生的事情, 有时可以带来便利, 而有时却会带来意想不到的后果. explicit关键字用来避免这样的情况发生。
// 加了explicit之后的代码
#include
using namespace std;
class Point {
public:
int x, y;
explicit Point(int x = 0, int y = 0)
: x(x), y(y) {}
};
void displayPoint(const Point& p)
{
cout << "(" << p.x << ","
<< p.y << ")" << endl;
}
int main()
{
displayPoint(Point(1));
Point p(1);
}
const字面意思为只读,可用于定义变量,表示变量是只读的,不可以更改,如果更改,编译期间就会报错。
主要用法如下:
const int value = 5;
char *const ptr; // 指针本身是常量
const char* ptr; // 指针指向的变量为常量
class A{};
void func(const A& a);
class A {
const int value = 5;
};
class B {
const int value;
B(int v) : value(v){}
};
class A{
void func() const;
};
class A {
void func() const;
};
const A a;
a.func();
提示:该文章还在更新中:
不要走开!作者还在持续更新中,建议点赞关注加收藏!
https://zhuanlan.zhihu.com/p/139515439
更多好文推荐
2021-4月Python 机器学习——中文新闻文本标题分类
2021年4月-(计算机网络)小型校园网络模拟搭建,最全最准确版
2022-10-31-基于用户的协同过滤推荐算法实现+MAE+RMSE
2022-11-28-大数据可视化,特征维度大于50
2023-3-9-一篇简短的文章把C++左右值关系讲的透透彻彻
上一篇 |
End
|
下一篇 |