烟台网站建设多少钱,wordpress用户图标,wordpress 导航标签,建设银行云南分行招聘网站这一部分介绍C17对已有标准库组件的拓展和修改。
1. 类型特征拓展
1.1 类型特征后缀_v
自从C17起#xff0c;对所有返回值的类型特征使用后缀_v#xff0c;例如#xff1a;
std::is_const_vT; // C17
std::is_constT::value; // C11这适用于所有返回值的…这一部分介绍C17对已有标准库组件的拓展和修改。
1. 类型特征拓展
1.1 类型特征后缀_v
自从C17起对所有返回值的类型特征使用后缀_v例如
std::is_const_vT; // C17
std::is_constT::value; // C11这适用于所有返回值的类型特征也可以用作运行时的条件表达式。
1.2 新的类型特征
特征效果is_aggregate是否是聚合体类型is_swappable该类型是否能调用swap()is_nothrow_swapable该类型是否能调用swap()并且不会抛出异常is_swappable_withT1,T2特定的两个类型是否能交换(swap())is_nothrow_swappable_withT1,T2特定的两个类型是否能交换(swap())并不会抛出异常has_unique_object_representations是否该类型的两个值相等的对象在内存中的表示也一样is_invocableT,Args…是否可以用Args...调用is_nothrow_invocableT,Args…是否可以用Args...调用并不会抛出异常is_invocable_rRT,T,Args…该类型是否可以用Args...调用且返回RT类型is_nothrow_invocable_rRT,T,Args…该类型是否可以用Args...调用且返回RT类型并且不会抛出异常invoke_resultT,Args…用Args...作为实参进行调用会返回的类型conjunctionB…对bool特征B...进行逻辑与disjunctionB…对bool特征B...进行逻辑或运算negation对bool特征B进行非运算is_execution_policy是否执行策略类型
另外is_literal_type和result_of自从C17起被废弃。
templatetypename T
struct D : string, complexT
{string data;
};struct C
{bool operator()(int) const{return true;}
};string foo(int);using T1 invoke_result_tdecltype(foo), int; // string// 是否是指针
templatetypename T
struct IsPtr : disjunctionis_null_pointerT, // 空指针is_member_pointerT, // 成员函数指针is_pointerT // 原生指针
{};int main()
{Dfloat s{ {hello},{4.5,6.7},world };cout is_aggregate_vdecltype(s) endl; // 1cout is_swappable_vint endl; // 1cout is_swappable_with_vint, int endl; // 1cout is_invocable_vC endl; // 0cout is_invocable_vC,int endl; // 1cout is_invocable_vint(*)() endl; // 1cout is_invocable_r_vbool, C, int endl; // 1}2. 并行STL算法
为了从现代的多核体系中受益C17标准库引入了并行STL算法来使用多个线程并行处理元素。
许多算法扩展了一个新的参数来指明是否要并行运行算法。
一个简单的计时器辅助类
设计一个计算器来测量算法的速度。
#include chrono/*****************************************
* timer to print elapsed time
******************************************/class Timer
{
private:chrono::steady_clock::time_point last;
public:Timer() : last{ chrono::steady_clock::now() }{}void printDiff(const std::string msg Timer diff: ){auto now{ chrono::steady_clock::now() };chrono::durationdouble, milli diff{ now - last };cout msg diff.count() ms\n;last chrono::steady_clock::now();}
};2.1 使用并行算法
2.1.1 使用并行的for_each()
#include numeric
#include execution // 执行策略包含并行模式
#include cmathint main()
{int numElems 1000000;struct Data{double value; // 初始值double sqrt; // 计算平方根};// 初始化numElems个还没有计算平方根的值vectorData coll;coll.reserve(numElems);for (int i 0; i numElems; i)coll.push_back(Data{ i * 4.37,0 });for (int i 0; i 5; i){Timer t;// 顺序计算平方根for_each(execution::seq, coll.begin(), coll.end(), [](auto val){val.sqrt sqrt(val.value);});t.printDiff(sequential: );// 并行计算平方根for_each(execution::par, coll.begin(), coll.end(), [](auto val){val.sqrt sqrt(val.value);});t.printDiff(parallel: );cout endl;}
}测试结果中只有少量元素的时候串行算法明显快得多。这是因为启动和管理线程占用了太多的事件。但是当元素很大的时候并行执行的效率就大大提升。
值得使用并行算法的关键在于
操作很长复杂有很多元素
2.1.2 使用并行sort()
排序是另一个并行算法的例子。
int main()
{int numElems 100000;vectorstring coll;for (int i 0; i numElems / 2; i){coll.emplace_back(id to_string(i));coll.emplace_back(ID to_string(i));}for (int i 0; i 5; i){Timer t;// 顺序排序sort(execution::seq, coll.begin(), coll.end(), [](const auto a,const auto b){return string_view{ a }.substr(2) string_view{ b }.substr(2);});t.printDiff(sequential: );// 并行排序sort(execution::par, coll.begin(), coll.end(), [](const auto a, const auto b){return string_view{ a }.substr(2) string_view{ b }.substr(2);});t.printDiff(parallel: );// 并行化乱序排序sort(execution::par_unseq, coll.begin(), coll.end(), [](const auto a, const auto b){return string_view{ a }.substr(2) string_view{ b }.substr(2);});t.printDiff(parallel unsequential: );cout endl;}
}2.2 执行策略
你可以像并行STL算法传递不同的执行策略作为第一个参数。定义在头文件execution中。
策略含义std::execution::seq顺序执行std::execution::par并行化顺序执行std::execution::par_unseq并行化乱序执行保证不会出现死锁
2.3 异常处理
当处理元素的函数因为未捕获的异常而退出时所有的并行算法会调用terminate()。
并行算法本身也可能会抛出异常。比如它们申请并行执行所需的临时内存资源时失败了可能会抛出bad_alloc异常。
2.4 并行算法概述
无限制的并行算法
find_end()adjacent_find()search()search_n()swap_ranges()replace()replace_if()fill()generate()remove()remove_if()unique()reverse()rotate()partition()stable_sort()partial_sort()is_sorted()is_sorted_until()nth_element()inplace_merge()is_heap()is_heap_until()min_element()max_element()min_max_element()
无并行版本的算法
accmulate()partial_sum()inner_product()search()copy_backward()move_backward()sample()shuffle()partition_point()lower_bound()upper_bound()equal_range()binary_serach()is_permutation()next_permutation()prev_permutation()push_heap()pop_hep()make_heap()sort_heap()
对于一下算法可以使用对应的并行算法来替代
accumulate()使用reduce()或者transform_reduce()inner_product()使用transform_reduce()
2.5 并行编程的新算法的动机
C17还引入了一些补充的算法来实现从C98就可以的标准算法的并行执行。
2.5.1 reduce()
reduce是accumulate()的并行化版本但是也有所不同下面分几个场景介绍
可结合可交换操作的并行化例如加法
void printSum(long num)
{vectorlong coll;coll.reserve(num * 4);for (long i 0; i num; i){coll.insert(coll.end(), { 1,2,3,4 });}Timer t;auto sum reduce(execution::par, coll.begin(), coll.end(), 0L);t.printDiff(reduce: );sum accumulate(coll.begin(), coll.end(), 0L);t.printDiff(accumulate: );cout sum: sum endl;
}int main()
{printSum(1);printSum(1000);printSum(1000000);
}不可交换操作的并行化浮点数相加
void printSum(long num)
{vectordouble coll;coll.reserve(num * 4);for (long i 0; i num; i){coll.insert(coll.end(), { 0.1,0.3,0.00001 });}Timer t;double sum1 reduce(execution::par, coll.begin(), coll.end(), 0.0);t.printDiff(reduce: );double sum2 accumulate(coll.begin(), coll.end(), 0.0);t.printDiff(accumulate: );cout (sum1 sum2 ? equal\n : differ\n);
}int main()
{cout setprecision(5); // 浮点数精度printSum(1); // equalprintSum(1000); // equalprintSum(1000000); // differ
}不可结合操作的并行化累积操作改为加上每个值的平方
void printSum(long num)
{vectorlong coll;coll.reserve(num * 4);for (long i 0; i num; i){coll.insert(coll.end(), { 1,2,3,4 });}auto squareSum [](auto sum, auto val) {return sum val * val;};auto sum accumulate(coll.begin(), coll.end(), 0L, squareSum);cout accumulate(): sum endl;auto sum1 reduce(execution::par,coll.begin(), coll.end(), 0L, squareSum);cout reduce(): sum endl;
}int main()
{printSum(1);printSum(1000);printSum(10000000);
}reduce()有可能会导致结果不正确。因为这个操作是不可结合的。假设有三个元素1、2、3。在计算过程中可能导致计算的是
(0 1 * 1) (2 3 * 3) * (2 3 * 3)
解决这个问题的方法是用另一个新算法transform_reduce()。把我们对每一个元素的操作和可交换的结果的累计都并行化。
void printSum(long num)
{vectorlong coll;coll.reserve(num * 4);for (long i 0; i num; i){coll.insert(coll.end(), { 1,2,3,4 });}auto sq [](auto val) {return val * val;};auto sum transform_reduce(execution::par, coll.begin(), coll.end(), 0L, plus{}, sq);cout transform_reduce(): sum endl;
}transform_reduce的参数
运行并行执行的策略要处理的值范围外层累积的初始值外层累积的操作在累积之前处理每个值的lambda表达式
2.5.2 transform_reduce()
使用transform_reduce()进行文件系统操作
int main(int argc, char* argv[])
{if (argc 2){cout Usage: argv[0] path \n;return EXIT_FAILURE;}namespace fs filesystem;fs::path root{ argv[1] };// 初始化文件树中所有文件路径的列表vectorfs::path paths;try{fs::recursive_directory_iterator dirpos{ root };copy(begin(dirpos), end(dirpos), back_inserter(paths));}catch (const exception e){cerr EXCEPTION: e.what() endl;return EXIT_FAILURE;}// 累积所有普通文件的大小auto sz transform_reduce(execution::par,paths.cbegin(), paths.cend(),uintmax_t{ 0 },plus(),[](const fs::path p){return is_regular_file(p) ? file_size(p) : uintmax_t{ 0 };});cout size of all paths.size() regular files: sz endl;
}3. 新的STL算法详解
3.1 for_each_n()
作为并行STL算法的一部分原本的for_each_n又有了一个新的并行版本。类似于copy_n()、fill_n()、generate_n()这个算法需要一个整数参数指出要对范围内多少个元素进行操作。
InputIterator
for_each_n (ExecutionPolicy pol,InputIterator beg,Size count,UnaryProc op);以beg为起点的前count个元素调用op。返回最后一个调用op元素的下一个位置。调用者必须保证有足够多的元素。op返回的任何值都会被忽略。如果没有传递执行策略其他参数按顺序传递即可。如果传入了第一个可选的执行策略参数 对所有元素调用的op的顺序没有任何保证。调用者要保证并行操作不会产生数据竞争。迭代器必须至少是前向迭代器。
例如
int main()
{vectorstring coll;for (int i 0; i 10000; i){coll.push_back(to_string(i));}// 修改前五个元素for_each_n(coll.begin(), 5,[](auto elem){elem value elem;});for_each_n(coll.begin(), 10,[](const auto elem){cout elem endl;});
}3.2 新的STL数值算法
这些算法都定义在numeric中。
3.2.1 reduce()
typename iterator_traitsInputIterator::value_type
reduce (ExecutionPolicy pol, // 可选的InputIterator beg, InputIterator end);T
reduce (ExecutionPolicy pol, // 可选的InputIterator beg, InputIterator end,T initVal);T
reduce (ExecutionPolicy pol, // 可选的InputIterator beg, InputIterator end,T initVal,BinaryOp op);3.2.2 transform_reduce()
变种一
T
transform_reduce (ExecutionPolicy pol, // 可选的InputIterator beg,InputIterator end,T initVal,BinaryOp op2,UnaryOp op1)initVal op2 op1(a1) op2 op1(a2) op2 op1(a3) op2 ...
变种二
T
transform_reduce (ExecutionPolicy pol, // 可选的InputIterator beg1, InputIterator end1,InputIterator beg2,T initVal);T
transform_reduce (ExecutionPolicy pol, // 可选的InputIterator beg1, InputIterator end1,InputIterator beg2,T initVal,BinaryOp1 op1, BinaryOp2 op2);第一个形式计算范围beg1和beg2开始的元素相乘再加上initVal。initVal elem1 * elem2。第二个形式是对beg1和beg2开始的元素调用op2然后对initVal和上一步的结果调用op1。initVal op1(initVal, op2(elem1, elem2))。
3.2.3 inclusive_scan()和exclusive_scan()
OutputIterator
inclusive_scan (ExcutionPolicy pol, // 可选的InputIterator inBeg, InputIterator inEnd,OutputIterator outBeg,BinaryOp op, // 可选的T initVal // 可选的);OutputIterator
exclusive_scan (ExcutionPolicy pol, // 可选的InputIterator inBeg, InputIterator inEnd,OutputIterator outBeg,T initVal, // 必须的BinaryOp op // 可选的);所有的形式是在计算范围[inBeg, inEnd)内每个元素和之前所有元素组合之后的值并写入以outBeg开头的目标范围。对于值a1 a2 a3 ... aNinclusive_scan()计算initVal op a1, initVal op a1 op a2, initVal op a1 op a2 op a3, ...aN对于值a1 a2 a3 ... aNexclusive_scan()计算initVal, initVal op a1, initVal op a1 op a2, ... aN-1所有的形式都返回最后一个被写入的位置的下一个位置。如果没有传递op会使用plus。如果没有传递initVal将不会添加初始值。第一个输出的值将直接是第一个输入的值。op不能修改传入的参数。
int main()
{array coll{ 3,1,7,0,4,1,6,3 };cout inclusive scan(): ;inclusive_scan(coll.begin(), coll.end(),ostream_iteratorint(cout, ));cout \n exclusive_scan(): ;exclusive_scan(coll.begin(), coll.end(),ostream_iteratorint(cout, ),0);cout \n inclusive scan(): ;inclusive_scan(coll.begin(), coll.end(),ostream_iteratorint(cout, ),plus{},100);cout \n exclusive_scan(): ;exclusive_scan(coll.begin(), coll.end(),ostream_iteratorint(cout, ),100, plus{});
}输出结果 inclusive scan(): 3 4 11 11 15 16 22 25exclusive_scan(): 0 3 4 11 11 15 16 22inclusive scan(): 103 104 111 111 115 116 122 125exclusive_scan(): 100 103 104 111 111 115 116 1223.2.4 transform_inclusive_scan()和transform_exclusive_scan()
OutputIterator
transform_inclusive_scan (ExcutionPolicy pol, // 可选的InputIterator inBeg, InputIterator inEnd,OutputIterator outBeg,BinaryOp op2, // 必须的UnaryOp op1, // 必须的T initVal // 可选的);OutputIterator
transform_exclusive_scan (ExcutionPolicy pol, // 可选的InputIterator inBeg, InputIterator inEnd,OutputIterator outBeg,T initVal, // 必须的BinaryOp op2, // 必须的UnaryOp op1, // 必须的 );对于值a1 a2 a3 ... aNtransform_inclusive_scan计算initVal op2 op1(a1), initVal op2 op1(a1) op2 op1(a2), ... op2 op1(aN)。对于值a1 a2 a3 ... aNtransform_exclusive_scan计算initVal, initVal op2 op1(a1), initVal op2 op1(a1) op2 op1(a2), ... op2 op1(aN-1)。
int main()
{array coll{ 3,1,7,0,4,1,6,3 };auto twice [](int v) { return v * 2; };cout source: ;copy(coll.begin(), coll.end(), ostream_iteratorint(cout, ));cout \n transform_inclusive_scan(): ;transform_inclusive_scan(coll.begin(), coll.end(),ostream_iteratorint(cout, ),plus{}, twice);cout \n transform_inclusive_scan(): ;transform_inclusive_scan(coll.begin(), coll.end(),ostream_iteratorint(cout, ),plus{}, twice, 100);cout \n transform_exclusive_scan(): ;transform_exclusive_scan(coll.begin(), coll.end(),ostream_iteratorint(cout, ),100, plus{}, twice);
}4. 子串和子序列搜索器
4.1 使用子串搜索器
新的搜索器主要是用来在长文本中搜索字符串例如单词或者短语。因此首先演示一下在什么情况下使用它们以及带来的改变。
4.1.1 通过search()使用搜索器 字符串成员函数find() size_t idx text.find(sub);算法search() auto pos std::search(text.begin(), text.end(), sub.begin(), sub.end());并行算法search() auto pos std::search(execution::par, text.begin(), text.end(), sub.begin(), sub.end());使用default_searcher auto pos search(text.begin(), text.end(), default_searcher{ sub.begin(),sub.end() });使用boyer_moore_searcher auto pos search(text.begin(), text.end(), boyer_moore_searcher{ sub.begin(),sub.end() });使用boyer_moore_horspool_searcher auto pos search(text.begin(), text.end(), boyer_moore_horspool_searcher{ sub.begin(),sub.end() });Boyer-Moore和Boyer-Moore-Horspool搜索器是非常著名的在搜索之前预计算“表”存放哈希值的算法当搜索区间非常大时可以显著加快搜索速度。算法需要随机访问迭代器。
搜索器的性能
只使用非并行版本的search()通常是最慢的方法因为对于text中的每个字符我们都要查找以它开头的子串是否匹配搜索目标。使用默认搜索器应该与上一周方法差不多但是实际最差能比上一种慢三倍。使用find()可能会更快。这依赖于标准库实现的质量。如果文本或者要搜索的子串非常长boyer_moore_searcher应该是最快的方法。和search()相比甚至可能提供50倍到100倍左右的性能。boyer_moore_horspool_searcher用时间换空间虽然比boyer_moore_searcher慢但占用的内存会更少。使用并行search()的速度是普通的三倍但远远小于新增的搜索器。
4.1.2 直接使用搜索器
另一个使用Boyer-Moore搜索器的方法是你可以直接使用搜索器的函数调用运算符会返回一个匹配子序列开头和尾后迭代器的pair。
boyer_moore_searcher bmsearch{ sub.begin(),sub.end() };for (auto begend bmsearch(text.begin(), text.end()); begend.first ! text.end(); begend bmsearch(begend.second, text.end()))
{cout found sub at index begend.first - text.begin() - begend.second - text.begin() endl;
}4.2 使用泛型子序列搜索器
原本这种算法使作为字符串搜索器开发的。然而C17将它们改进为泛型算法因此你可以在一个容器或者范围内搜索子序列。
int main()
{vectorint coll;dequeint sub{ 0,8,15,... };auto pos search(coll.begin(), coll.end(), boyer_moore_searcher{ sub.begin(),sub.end() });// 或者下面boyer_moore_searcher bm{ sub.begin(),sub.end() };auto [beg, end] bm(coll.begin(), coll.end());if (beg ! coll.end())cout found subsequence at beg - coll.begin() endl;
}想要能够使用该算法元素必须能用在哈希表中也就是必须提供默认的哈希函数和比较符。否则使用搜索器谓词。
4.3 搜索器谓词
当使用搜索器时你可以使用谓词。出于两个原因
你想自定义两个元素的比较方式你想提供一个自定义的哈希函数也是Boyer-Moore搜索器必须的。
例如这里用一个大小写不敏感的搜索器来搜索子串
boyer_moore_searcher bmic { sub.begin(), sub.end(),[](char c){return hashchar{}(toupper(c));},[](char c1,char c2){return toupper(c1) toupper(c2);}};5. 其他工具函数和算法
5.1 size()/empty()/data()
C新增加了三个辅助函数size()、empty()、data()。都定义在iterator头文件中。
5.1.1 泛型size()函数
该函数允许我们查任何范围的大小前提是由迭代器接口或者是原生数组。
templatetypename T
void printLast5(const T coll)
{auto size{ size(coll) };cout size elems: ;auto pos{ begin(coll) };// 将迭代器递增到倒数第五个元素处if (size 5){advance(pos, size - 5);cout ...;}// 打印剩下的元素for (; pos ! end(coll); pos){cout *pos ;}cout endl;
}5.1.2 泛型empty()函数
类似size()empty()可以检查容器、原生数组、initializer_list是否为空。
5.1.3 泛型data()函数
data()函数允许我们访问集合的原始数据。
5.2 as_const()
新的辅助函数as_const()可以在不使用static_cast或者add_const_t类型特征的情况下把值转换为响应的const类型。
vectorstring coll;foo(coll); // 调用非常量版本
foo(as_const(coll)); // 调用常量版本5.2.1 以常量引用捕获
例如
int main()
{vectorint coll{ 8,5,7,42 };auto printColl [coll as_const(coll)]{cout coll: ;for (int elem : coll){cout elem ;}cout endl;};
}5.3 clamp()
C17提供了一个新的工具函数clamp()找出三个值大小居中的那个。
int main()
{for (int i : {-7, 0, 8, 15}){cout clamp(i, 5, 13) endl;}
}5.4 sample()
C17提供了sample()算法来从一个给定的范围内提取一个随机的子集。有时候也被称为水塘抽样或者选择抽样。
#include randomint main()
{vectorstring coll;for (int i 0; i 10000; i){coll.push_back(value to_string(i));}sample(coll.begin(), coll.end(),ostream_iteratorstring(cout, \n),10,default_random_engine{});
}该函数有以下保证和约束
源范围迭代器至少是输入迭代器目标迭代器至少是输出迭代器。如果源迭代器表示输入迭代器那么目标迭代器必须是随机访问迭代器。如果目标区间大小不够有没有使用插入迭代器那么写入目标迭代器可能会导致未定义行为。算法返回最后一个被拷贝元素的下一个位置。目标迭代器指向的区间不能在源区间中。num可能是整数类型。如果源区间的元素数量不足将会提取源区间的所有元素。只要源区间的迭代器不只是输入迭代器那么提取的子集中的顺序保持稳定。
int main()
{vectorstring coll;for (int i 0; i 10000; i){coll.push_back(value to_string(i));}// 用一个随机数种子初始化Mersenne Twister引擎random_device rd;mt19937 eng{ rd() };vectorstring subset;subset.resize(100);auto end sample(coll.begin(), coll.end(),subset.begin(),10,eng);for_each(subset.begin(), end, [](const auto s){cout random elem: s endl;});
}6. 容器和字符串扩展
6.1 节点句柄
C17引入把某个节点从关联或无需容器中移除或移入的功能。
6.1.1 修改key
int main()
{mapint, string m{ {1,mango},{2,papaya},{3,guava} };auto nh m.extract(2); // nh的类型为decltype(m)::node_typenh.key() 4;m.insert(move(nh));for (const auto [key, value] : m){cout key : value endl;}
}这段代码是把key为2的的元素结点移除了容器然后修改了key最后有移进了容器。C17之前如果想修改一个key必须删除旧结点然后再插入一个value相同的新节点。如果我们使用节点句柄将不会发送内存分配。
6.1.2 在容器之间移动节点句柄
你也可以使用节点句柄把一个元素从一个容器move到另一个容器。
templatetypename T1,typename T2
void print(const T1 coll1, const T2 coll2)
{cout values:\n;for (const auto [key, value] : coll1){cout [ key : value ];}cout endl;for (const auto [key, value] : coll2){cout [ key : value ];}cout endl;
}int main()
{multimapdouble, string src{ {1.1,one},{2.2,two},{3.3,three} };mapdouble, string dst{ {3.3,old data} };print(src, dst);dst.insert(src.extract(src.find(1.1)));dst.insert(src.extract(2.2));print(src, dst);
}6.1.3 合并容器
以节点句柄API为基础现在所有的关联和无需容器都提供了成员函数merge()可以把一个容器中的所有元素合并到另一个容器中。如果源容器中的某个元素和目标容器的元素的key值相同那么它仍然保留在源容器中。
templatetypename T1,typename T2
void print(const T1 coll1, const T2 coll2)
{cout values:\n;for (const auto [key, value] : coll1){cout [ key : value ];}cout endl;for (const auto [key, value] : coll2){cout [ key : value ];}cout endl;
}int main()
{multimapdouble, string src{ {1.1,one},{2.2,two},{3.3,three} };mapdouble, string dst{ {3.3,old data} };print(src, dst);// 把src的所有元素和并到dst中dst.merge(src);print(src, dst);
}6.2 emplace改进
6.2.1 emplace函数的返回类型
对于顺序容器vector、deque、list、forward_list还有容器适配器stack和queue。他们的emplace函数现在返回新插入的对象的引用。例如
foo(myVector.emplace_back(...));
// 等同于
myVector.emplace_back(...);
foo(myVector.back());6.2.2 map的try_emplace()和insert_or_assign()
try_emplace()用移动语义构造了一个新的值。insert_or_assign()稍微改进了插入/更新元素的方法。
6.2.3 try_emplace()
考虑如下代码
mapint,string m;
m[42] hello;
string s{world};
m.emplace(42,move(s)); // 可能移动但42已经存在了可能不会移动这样调用之后s是否保持原本的值是未定义的。同样使用insert()之后也可能是这样。
m.insert({42,move(s))});新的成员函数try_emplace()保证在没有已经存在元素时才会move走传入的值
m.try_emplace(42,move(s)); // 如果插入失败不会move6.2.4 insert_or_assign()
另外新的成员函数insert_or_assign()保证把值移动道一个新的元素或者已经存在的元素中。
m.insert_or_assgin(42,move(s)); // 总是会move6.3 对不完全类型的容器支持
自从C17起vector、list、forward_list被要求支持不完全类型。
你现在可以定义一个类型内部递归的包含一个自身类型的容器。例如
struct Node
{string value;vectorNode children;
};6.4 string的改进
对于非常量string()你现在可以调用data()吧底层的字符序列当做原生C字符串来访问。
auto cstr mystring.data();
cstr[6] w; // OKchar *cstr mystring.data(); // OK7. 多线程和并发
7.1 补充的互斥量和锁
7.1.1 scoped_lock
C11引入了一个简单的lock_guard来实现RAII风格的互斥量上锁
构造函数上锁析构函数解锁
不幸的是没有标准化的可变参数模板可以用来在一条语句中同时锁住多个互斥量。
scoped_lock解决了这个问题。它允许我们同时锁住一个或多个互斥量互斥量的类型可以不同。
int main()
{vectorstring alllssues;mutex alllssuesMx;vectorstring openlssues;timed_mutex openlssuesMx;// 同时锁住两个issue列表{scoped_lock lg(alllssuesMx, openlssuesMx);// .. 操作}
}类似于一下C11代码
// 同时锁住两个issue列表
{lock(alllssuesMx, openlssuesMx); // 避免死锁的方式上锁lock_guardmutex lg1(alllssuesMx, adopt_lock);lock_guardtimed_mutex lg2(openlssuesMx, adopt_lock);// .. 操作
}因此当传入的互斥量超过一个时scoped_lock的构造函数会使用可变参数的快捷函数lock()这个函数会保证不会导致死锁。
注意你也可以传递已经被锁住的互斥量
// 同时锁住两个issue列表
{lock(alllssuesMx, openlssuesMx); // 避免死锁的方式上锁scoped_lock lg{adpot_lock, alllssuesMx, openlssuesMx};// .. 操作
}7.1.2 shared_mutex
C14添加了一个shared_timed_mutex来支持读/写锁它支持多个线程同时读一个值偶尔会有一个线程更改值。现在引入了shared_mutex定义在头文件shared_mutex。
支持以下操作
对于独占锁lock()、try_lock()、unlock()对于共享的读访问lock_shared()、try_lock_shared()、unlock_shared()native_handle()
假设你有一个共享的vector被多个线程读取偶尔会被修改
vectordouble v; // 共享的资源
shared_mutex vMutex;int main()
{if (shared_lock sl(vMutex); v.size() 0){// 共享读权限}{scoped_lock sl(vMutex);// 独占写权限}
}7.2 原子类型的is_always_lock_free
你现在可以使用一个C库的特性来检查一个特定的原子类型是否总是可以在无锁的情况下使用。例如
if constexpr(atomicint::is_always_lock_free)
{// ...
}
else
{// ...
}如果一个原子类型的is_always_lock_free返回true那么该类型的对象is_lock_free()成员也一定会返回true
if constexpr(atomicT::is_always_lock_free)
{assert(atomicT{}.is_lock_free()); // 绝不会失败
}在C17之前只能使用相应的宏来判断例如当ATOMIC_INT_LOCK_FREE返回2的时候相当于is_always_lock_free返回true。
7.3 cache行大小
程序有时候需要处理cache行大小的能力有以下两种
不同线程访问的不同对象不属于同一个cache是很重要的。否则不同线程并行访问对象时cache行缓存的内存可能需要同步。你可能会想把多个对象放在同一个cache行中这样访问了第一个对象之后访问接下来的对象就可以直接在cache行中访问。
C标准库在头文件中引入了两个内联变量
namespace std
{inline constexpr size_t hardware_destructive_interference_size; // 可能被不同线程并发访问的两个对象之间的最小偏移量inline constexpr size_t hardware_constructive_interference_size; // 两个想被放在同一个L1缓存行的对象合起来的最大大小
}如果你想要在不同的线程里访问两个不同(原子)的对象
struct Data
{alignas(std::hardware_destructive_interference_size) int valueForThreadA;alignas(std::hardware_destructive_interference_size) int valueForThreadB;
};如果你想要在同一个线程里访问不同的(原子)对象
struct Data {int valueForThraedA;int otherValueForTheThreadA;
};8. 标准库中其他微小的修改和特性
8.1 std::uncaught_exceptions()
C中存在一个模式RAII这是一种安全处理那些你必须要释放或清理的资源的方式。在构造一个对象的时候将所需要的资源分配给它在析构中将分配的资源进行释放。
然而有的时候资源的“释放操作”依赖于我们到底是正常执行离开了作用域还是因为异常离开了作用域。一个例子是事务性资源如果我们正常执行离开了作用域我们可能想进行提交操作而当因为异常离开作用域时想进行回滚操作。为了达到这个目的C11引入了std::uncaught_expection()用法如下
class Request
{
public:~Request(){if(std::uncaught_expection()) rollback(); // 如果没有异常情况会调用rollback()else commit();}
};然而在如下场景中使用这个API不能正常工作当我们正在处理异常时如果创建了新的Request对象那么即使在使用它的期间没有异常抛出它的析构函数总是会调用rollback()。
try
{...
}
catch(...)
{Request r2{...};
}C17解决了这个问题对Request类进行以下修改
class Request
{
private:int initialUncaught{std::uncaught_expections()};
public:~Request(){if(std::uncaught_expections() initialUncaught) rollback(); // 如果没有异常情况会调用rollback()else commit();}
};旧的不带s的API自C17起被废弃不应该再被使用。
8.2 共享指针的改进
C17添加了一些共享指针的改进且成员函数unique()已经被废弃了。
8.2.1 对原生C数组的共享指针的特殊处理
自从C17起可以对其使用特定的deleter函数C11unique_ptr已经可以做到了例如
std::shared_ptrstd::string p{new std::string[10], [](std::string* p){delete[] p;
}};当实例化是数组时不再使用operator*而是使用operator[]
std::shared_ptrstd::string ps{new std::string};
*ps hello; // OK
ps[0] hello; // ERRORstd::shared_ptrstd::string[] parr{new std::string[10]};
*parr hello; // ERROR
parr[0] hello; // OK8.2.2 共享指针的reinterpret_pointer_cast
除了static_pointer_cast、dynamic_pointer_cast、const_pointer_cast之外可以调用reinterpret_pointer_cast。
8.2.3 共享指针的weak_type
为了支持在泛型代码中使用弱指针共享指针提供了一个新的成员weak_type。例如
templatetypename T
void ovserve(T sp)
{typename T::weak_type wp{sp};
}8.2.4 共享指针的weak_from_this
C17起有一个额外的辅助函数可以返回一个指向对象的弱指针
Person *pp new Person{};
std::shared_ptrPerson sp{pp};std::weak_ptrPerson wp{pp-weak_from_this()}; // wp分享了sp拥有的所有权8.3 数学拓展
C17引入了以下的数学函数。
8.3.1 最大公约数和最小公倍数
在头文件numeric中
gcd(x, y) 返回x和y的最大公约数lcm(x, y) 返回x和y的最小公倍数
8.3.2 std::hypot()的三参数重载
在头文件cmath中
hypot(x, y, z) 返回三个参数的平方之和的平方根
8.3.3 数学中的特殊函数
名称含义assoc_laguerre()关联Laguerre多项式assoc_legendre()关联Legendre函数beta()beta函数comp_ellint_1()第一类完整椭圆积分comp_ellint_2()第二类完整椭圆积分comp_elint_3()第三类完整椭圆积分cyl_bessel_i()规则圆柱贝塞尔函数cyl_bessel_j()第一类圆柱贝塞尔函数cyl_bessel_k()不规则圆柱贝塞尔函数变体cyl_neumann()圆柱诺依曼函数ellint_1()第一类不完整椭圆积分elint_2()第二类不完整椭圆积分elint_3()第三类不完整椭圆积分expint()指数积分hermite()Hermite多项式laguerre()Laguerre多项式legendre()Legendre多项式riemann_zeta()黎曼zeta函数sph_bessel()第一类球形贝塞尔函数sph_legendre()关联球形Legendre函数sph_neumann()球形诺依曼函数
8.3.4 chrono拓展
对于时间段和时间点添加了新的舍入函数
round(): 舍入到最近的整数值floor(): 向负无穷舍入到最近的整数值(向下取整)ceil(): 向正无穷舍入到最近的整数值(向上取整)
8.3.5 constexpr拓展和修正
最重要的修正有
对于std::array下面的函数是constexpr begin()、end()、cbegin()、cend()、rbegin()、rend()、crbegin()、crend()非常量数组的operator[]、at()、front()、back()data() 范围访问的泛型独立函数和辅助函数。类std::reverse_iterator和std::move_iterator的所有操作C标准库整个时间库的部分。所有的std::char_traits特化的成员函数。
8.3.6 noexpect拓展和修正
最重要的修正有
std::vector和std::stringC17保证下列操作不会抛出异常 默认构造函数移动构造函数以分配器为参数的构造函数 对于所有的容器下列操作不会抛出异常 移动赋值运算符swap函数