优势
模块长远来看是要替换头文件
使用模块必须明确的说明要从模块中导入的内容, 例如要导出哪个类,函数,常亮,枚举
有模块接口文件和模块实现文件, 因此可将代码分成接口文件和实现文件, 类似头文件和cpp文件,对模块来说已经不再是必需的,对于模块接口文件,只有函数签名是导出内容, 因此即使在模块接口文件中编写了任何函数体,它们也不会被导出,不在导出的接口中, 因此如果这个函数被修改,不会触发引入你模块的用户重新编译
如果想为模块添加更多结构,可以做两件事, 可以使用子模块, 或者使用分区
不需要防卫式
不同的模块可以有相同的函数名字,当然两个模块导入同一个源文件那么会有链接错误
模块在处理时只会被构建一次, 预处理器宏不会对模块里的diamante产生任何影响,在模块里定义的所有预处理器宏都不会泄露出去, 因此模块的导入顺序并不重要
Create a module:
//cppcon.cppm -- Module Interface File
export module cppcon;//模块声明
namespace CppCon{
auto GetWelcomeHepler(){return "Welcom to CppCon 20";}//没被导出
export auto GetWelcome(){return GetWelcomeHelper();}//被导出
}
如何使用这个模块:
Consume a module:
//main.cpp
import cppcon;
int main(){
std::cout << CppCon::GetWelcome();
}
场景:对vector容器内的元素进行排序
//before
vector data{11, 22, 33};
sort(begin(data), end(data));
//now
ranges::sort(data);
消除不匹配的begin/end迭代器的错误
ranges的适配器能延迟转换/过滤数据,再传给算法
Ranges的主要组成部分
Range: 所有支持begin/end的标准库容器都是Ranges
Range-based algorithms:几乎所有以前接受迭代器对的标准库算法都能加在Ranges上
Projection:在交给算法之前,转换容器中的元素
Views:延期执行transform/filter元素, 视图不拥有任何东西,也无法修改底层数据
Range factories:建立range工厂按需生成值,如Range工厂可以产生整数无穷序列
Pipelining:可以将几个转换或过滤串联起来成为一个流水线管道
Note:延迟执行: 在构造result对象时没有事情被真正执行, 只有在便利结果对象中元素时, 整个流水线才被执行
因为有这种延迟执行, 我们可以使用所谓的无限序列
使用无穷序列:
auto result{ view::ints(10)//创建一个从10开始的无穷序列
| views::filter([](const auto& value){return value % 2 == 0;})
/*....执行一系列操作....*/
| views::take(10)//取前10个限制结果数量
};
协程来做什么?
编写所谓的生成器(Generators), 异步I/O, 延迟计算, 事件驱动应用
Example(VC++):
experimental::generator<int> GetSequenceGenerator(
int startValue, size_t numberOfValues)
{
for(int i{startValue}; i < startValue + numberOfValue; ++i){
time_t t{system_clock::to_time_t(system_clock::now())};
cout<< std::ctime(&t);
co_yield i;
}
}
int main()
{
auto gen{GetSequenceGenerator(10, 5)};
for(const auto& value : gen){
cout << value << "(Press enter for next value)" << endl;
cin.ignore();
}
}
创建一个concept:
template<typename T>
concept Incrementable = requires(T x){x++; ++x;};
//要求类型T同时支持前置++和后置++运算符
//对于类型T, x++和++x都应该能被编译
//这不是运行时要执行的代码, 而是在编译器评估
如何使用这个concept
方法一: 代替typename T
template<Incrementable T>
void Foo(T t);
方法二: 使用requires字句
template<typename T> requires Incrementable<T>
void Foo<T t>;
requires字句也可以在函数签名之前或者之后
template<typename T>
void Foo<T t> requires Incrementable<T>;
方法三:与简明函数模板语法结合
void Foo(Incrementable auto t);
我们写一个更复杂的concept
要求有一个不抛出异常的swap方法, 有一个返回size_t的size方法
template <typename T>
concept C = requires (T& x, T& y){
{x.swap(y)} noexcept;
{x.size()} -> std::convertible_to<std::size_t>;
}
模板要求, x可以使用swap()且不抛出异常,
x能使用size()且返回值能转换成std::size_t
当然现有的concept是可以任意组合
template<typename T> requires Incrementable<T> && Decrementable<T>
void Foo(T t);
也可以创建一个新的concept
template<typename T>
concept C = Incrementable<T> && Decrementable<T>;
void Foo(C auto t);//简明模板语法
[]<typename T> (T x){/*........*/}
[]<typename T> (T* p){/*........*/}
[]<typename T, int N> (T (&a)[N]){/*........*/}
before C++20
[](const auto &vec){
using V = std::decay_t<decltype(vec)>;
using T = typename V::value_type;
T x{};
T::static_function();
//...
};
now C++20:
[]<typename T> (const vector<T>& vec){
T x{};
T::static_function();
//...
};
before C++20
[](auto&& ...args){
return foo(std::forward<decltype(args)>(args)...);
}
now C++20
[]<typename ...T>(T&& ...args){
return foo(std::forward<T>(args)...);
}
template<typename T> class concurrent_stack{
struct Node{ T t; shared_ptr<Node> next;};
atomic_shared_ptr<Node> head;
//C++11: 使用shared_ptr , 在每次使用head时
//都要调用全局非成员函数
public:
class reference{
shared_ptr<Node> p;
};
auto find(T t) const{
auto p = head.load();
//C++11:atomic_load(&head)
while(p && p->t != t)
p = p->next;
return reference(move(p));
}
auto front() const{
return reference(head);
//C++11:atomic_load(&head)
}
void push_front(T t){
auto p = make_shared<Node>();
p->t = t;
p->next = head;//C++11:atomic_load(&head)
while(!head.compare_exchange_weak(p->next, p)){}
//C++11:atomic_compare_exchange_weak(&head, &p->next, p)
}
void pop_front(){
auto p = head.load();
while(p && !head.compare_exchange_weak(p, p->next)){}
//C++11:atomic_compare_exchange_weak(&head, &p, p->next)
}
};
显然并没办法阻止用户直接使用shared_ptr, 一旦使用了会导致线程不安全
如何cancel一个线程:
e.g:
void DoWorkPreCpp20(){
std::thread job{[]{/*.......*/}}
//旧版本的thread对象并行 运作某些事情
try{
//.. Do something else ...
//在做其他事情的时候, 可能发生异常,
//发生异常时必须要保证线程被join
//因此必须把这块代码放在try/catch中
}catch(...){
job.join();
throw;//rethrow
}
job.join();
}
在C++20之后:
void DoWord(){
std::jthread job{[]{/*......*/}};
//... Do something else ...
}//jthread对象在析构函数中自动取消线程并调用join()
线程协作式取消:
std::jthread job{[](std::stop_token token){//线程传入参数std::stop_token
while(!token.stop_requested()){//定期检查以停止线程
/*......*/
}
}};
........
job.request_stop();//在外部停止线程
也可以通过std::stop_source 停止线程:
std::stop_source source{job.get_stop_source()};
source.request_stop();
最后通过std::stop_token可以知道是否已经请求停止
std::stop_token token{job.get_stop_token()};
bool b{tken.stop_requested()};
结构体指定初始化
struct Data{
int anInt {0};
std::string aString;
};
Data d{.aString = "Hello"};
int i{42};
strong_ordering result{i <=> 0};
if(result == strong_ordering::less){cout << "less";}
if(result == strong_ordering::greate){cout << "greater";}
if(result == strong_ordering::equal){cout << "equal";}
此外还有命名的比较函数:
if(is_lt(result)){cout << "less";}
if(is_gt(result)){cout << "greater";}
if(is_eq(result)){cout << "equal";}
auto m {ctre::math<"[a-z]+([0-9]+)">(str)};
switch (value){
case 1:
break;
[[likely]] case 2:
break;
[[unlikely]] case 3:
break;
}
例子中,如果你知道2的可能性要比其他分支高, 可以在2处放置属性,
如果3的可能性比其他任何情况都小, 可以在3处放置属性
if语句分支也可以如此
Creating a year:
auto y1{ year {2020} };//使用构造函数语法
auto y2 { 2020y };//使用标准自定义字面量
Creating a month://创建月可以用构造函数构造,或12个预定义实例
auto m1{ month {9} };
auto m2{ Septerber };
Creating a day:
auto d1{ day{15} };
auto d2{ 15d };
Creating a full date:
year_month_day fulldate1 { 2020y, September, 15d };
auto fulldate2{ 2020y / Septermber / 15d };
year_month_day fulldate3{ Monday[3]/September/2020};
//创建2020年9月的第三个星期一
weeks w {1};//一个星期
days d {w};// 将一个星期转换为天
这里有个日期, sys_days是别名, 实际上是将其转换成time_point
system_clock::time_point t{
sys_days{2020y / September / 15d}};//date转time_point
另一方面, 也可以将time_pont转换成date
auto yearmothday{
year_moth_day{ floor<days>(t) } };//time_point转date
Date+Time:
auto utc{ sys_days{2020y/September/15d} + 9h + 35min + 10s};
//2020-09-15 09:35:10 UTC
可以将utc转换为世界上任何时区
zoned_time denver {"America/Denver", utc};//转换时区
查询当前时区是什么, 然后创建一个时区
auto localt { zoned_time { current_zone(), system_clock::now()}};
int data[42];
span<int, 42> a {data}; // fixed-size: 42 ints
span<int> b {data}; // dynamic-size: 42 ints
span<int, 50> c {data};//指定错误大小compilation error
span<int> d {ptr, len};//dynamic-size: len ints
通过这些 span 可以对data数组进行读写
构建一个只读的span:
span<const int> b;
E.g:
#if __has_include()
#include
#if __cpp_lib_optional
#define has_optional 1
#endif
#elif __has_include()
#include
#if __cpp_lib_experimental_optioanl
#define has_optional 1
#define optional_is_experimental 1
#endif
#endif
C++20之前:
constexpr auto InchToMm(double inch){ return inch * 25.4;}
函数InchToMm被标记为constexpr
const double const_inch{6};
const auto mm1 {InchToMm(const_inch)};
如果调用这个函数, 使用const double, 此函数将在编译期执行
double dynamic_inch{8};
const auto mm2{InchToMm(dynamic_inch)};
如果用double调用, 而不是const double, 那依然在运行时运行,
依然编译通过, 或许并不是想要的
C++20:
consteval auto InchToMm(double inch) {return inch * 25.4;}
const double const_inch{6};
const auto mm1 {InchToMm(const_inch)};//Fine
double dynamic_inch{8};
const auto mm2{InchToMm(dynamic_inch)};//error
constinit const char *a{...};
这里用了constinit, 如果这里有某种动态活动, 编译器会报错,
这样在运行时不会产生奇怪的结果
文字格式化C++20之前有两种方式
cout << format("{:=^19}, "CppCon 2020"); //====CppCon 2020====
cout << format("Read {0} bytes from {1}", n, "file1.txt");
cout << format("从{1}中读取{0}个字节", n, "file1.txt");
//这样也不会导致输出文本混乱
E.g:
void LogInfo(string_view info,
const source_location& location = source_location::current()){
cout << location.file_name() << ":" << location.line() << ":" << info <<endl;
}
int main(){
LogInfo("Welcome to CppCon 2020!");
}//打印信息可以实时输出代码信息, 不必使用宏来达成这个目的
[[nodiscard("Ignoring the return value will result in memory leaks.")]]
void* GetData(){
/*......*/
}
创建一个bitset对象,01100101向左移动2位:
std::bitset<8> b { std::rotl(0b01100101, 2)};
计算01100101中1位的个数:
int count {std::popcount(0b01100101)};
std::string str{"Hello world!"};
bool b{str.starts_with("Hello")};
std::map myMap{std::pair {1, "ons"s}, {2, "two"s}, {3, "three"s}};
bool result{myMap.contains(2)};
conscept 用于concept
requires 用于concept
constinit 用于const初始化
consteval 用于const运算
co_await 协程相关
co_return 协程相关
co_yield 协程相关
char8_t