关于c++11:lambda表达式

C++ 中的 Lambda 表达式是一种简洁的语法结构,容许在代码中间接定义和应用匿名函数。Lambda 表达式加强了代码的灵活性和可读性,特地是在解决算法、函数对象、事件处理、回调函数等场景下尤为有用。其个别格局如下: [capture_list](parameter_list) -> return_type { function_body }各局部具体阐明: 捕捉列表(Capture List): [] 是捕捉列表开始的标记,能够为空或者蕴含以下几种捕捉形式: = (值捕捉):捕捉内部作用域中的变量正本,对这些变量的批改不会影响原变量。 int x = 10;auto l = [=]() { return x * 2; }; // 捕捉x的值,即便之后x扭转,l仍返回20& (援用捕捉):捕捉内部作用域中变量的援用,对这些变量的批改会影响原变量。 int y = 20;auto m = [&y]() { ++y; return y; }; // 捕捉y的援用,调用m会减少y的值this 关键字:捕捉以后对象的指针,用于在成员函数外部拜访类的成员。不指定任何符号时,默认状况下是非动态局部变量的援用捕捉,同时也会捕捉所有通过值捕捉的内部变量。复合捕捉也是可能的,例如 [=,&z] 意味着除了 z 通过援用捕捉外,其余变量都通过值捕捉。 参数列表(Parameter List): 这部分和惯例函数参数列表相似,用于定义Lambda承受的输出参数。 auto add = [](int a, int b) { return a + b; };返回类型(Return Type): ...

February 23, 2024 · 1 min · jiezi

关于c++11:C11-uniquelock-lockguard

unique_lock独占的是mutex对象,就是对mutex锁的独占。用法如下:(1)新建一个unique_lock 对象(2)给对象传入一个std::mutex 对象作为参数; std::mutex mymutex;unique_lock lock(mymutex);加锁时新建一个对象lock,而这个对象生命周期完结后主动解锁。在函数外部创立一个锁,函数完结即主动开释unique_lock成员办法 lock()加锁unlock()解锁try_lock()尝试给互斥量加锁,如果拿不到锁,返回false,如果拿到了锁,返回true,这个函数是不阻塞的;release()返回它所治理的mutex对象指针,并开释所有权;也就是说,这个unique_lock和mutex不再有关系。严格辨别unlock()与release()的区别,不要混同。unique_lock所有权的传递 std::unique_lock<std::mutex> lock(mtx);//所有权概念lock领有mtx的所有权;lock能够把本人对mutex(mtx)的所有权转移给其余的unique_lock对象; unique_lock对象这个mutex的所有权是能够转移,然而不能复制。 std::mutex mtx;std::unique_lock<std::mutex> move_unique_lock(){ std::unique_lock<std::mutex> tmpguard(mtx); return tmpguard;//从函数中返回一个部分的unique_lock对象是能够的。挪动构造函数。 //返回这种举报对象tmpguard会导致系统生成长期unique_lock对象,并调用unique_lock的挪动构造函数}int main(){ std::mutex mtx; std::unique_lock<std::mutex> lock(mtx);//所有权概念 std::unique_lock<std::mutex> lock1(lock);//此句是非法的,复制所有权是非法的 std::unique_lock<std::mutex> lock2(std::move(lock));//挪动语义,当初先当与lock2与mtx绑定到一起.当初lock指向空,lock2指向了mtx std::unique_lock<std::mutex> lock3 = move_unique_lock();}

September 11, 2022 · 1 min · jiezi

关于c++11:CC实用工具内存相关问题排查工具cppcheck与valgrind

C++中令人纳闷的内存问题C++的内存问题时常令人非常困惑。总结起来C++的内存问题能够分为以下几类 内存泄露 当程序员应用new(或malloc)关键字分配内存而遗记应用 delete (或free)函数或 delete[] 运算符开释内存时,C++ 中就会产生内存透露。在 C++ 中应用谬误的删除运算符会产生内存透露最多的状况之一。delete 运算符利用于开释单个调配的内存空间,而 delete [] 运算符利用于开释数据值数组。内存透露对于很多不能停机的程序是致命的。它会导致内存使用量就一直减少,直至程序宕机。内存越界 内存越界的背地其实是拜访异样的内存地位。内存越界导致的问题往往让C++程序员非常困惑的问题,因为由内存越界导致程序呈现crash的地位往往不是真正导致程序crash的地位,这给排查内存越界带来很大的艰难。拜访未初始化对象 与内存越界相似,拜访未初始化的对象往往也会导致程序在其余地位crash。而产生crash的地位往往并不是导致crash起因。对于一个谨严的程序员是绝不能让内存拜访问题带到正式公布版本的。因而须要一些工具来帮忙咱们排查内存相干问题。本文的示例代码为内存透露。 动态查看工具---CppcheckCppCheck是一个C/C++代码缺点动态查看工具。不同于C/C++编译器及其它剖析工具,CppCheck只查看编译器查看不进去的bug,不查看语法错误。所谓动态代码查看就是应用一个工具查看咱们写的代码是否平安和强壮,是否有暗藏的问题。 Cppcheck的装置在Ubuntu下只须要运行sudo apt install cppcheck即可装置。装置胜利后,通过cppcheck --version命令,能够查看是否装置胜利,以及查看版本。作者装置的版本是1.82。 Cppcheck的应用cppcheck的应用也非常简略对于项查看的文件只须要输出cppcheck ${filename}即可。例如在内存透露示例中,对main.cpp进行动态代码查看,会发现如下问题: ~/Code/leak_example$ cppcheck main.cpp cppcheck main.cpp Checking main.cpp ...[main.cpp:13]: (error) Array 'b[10]' accessed at index 99, which is out of bounds.[main.cpp:35]: (error) Memory leak: a会发现在main.cpp第13行,呈现了数组越界的问题,同时会发现代码中没有回收在开始时调配的内存。对于源代码扩散在多个目录下的我的项目,能够间接把根目录作为cppcheck命令的参数,这样cppcheck就能够递归的查看该目录下所有的文件。 然而仅仅是动态代码检测是不足够的,细心地读者应该发现了其余的内存应用问题。事实上的确有很多问题不运行是难以发现的,因而须要valgrind进行更加齐备的内存拜访检测。 动静查看工具---valgrindValgrind 是一个用于构建动态分析工具的仪器框架。 Valgrind 工具能够自动检测许多内存治理和线程谬误,并详细分析您的程序。您还能够应用 Valgrind 构建新工具。 Valgrind 发行版目前包含七个生产品质工具:一个内存谬误检测器、两个线程谬误检测器、一个缓存和分支预测分析器、一个调用图生成缓存和分支预测分析器,以及两个不同的堆分析器。可见valgrind是一个十分弱小的CPP剖析工具,在本文中次要介绍如何用valgrind进行内存问题的排查。 valgrind装置在Ubuntu零碎中只需运行sudo apt install valgrind即可开启内存检测之旅。同样能够应用valgrind --version来查看是否装置胜利并查看版本。作者装置的版本是3.13.0。 valgrind应用和报告阐明在应用valgrind进行内存透露检测时肯定要用debug模式编译我的项目,否则valgrind无奈获取问题呈现的文件行数。编译完之后运行valgrind ./${executablefile}就能够对生成的可执行文件进行检测了。valgrind将会执行该文件,并记录下内存应用问题的地位。在咱们检测示例失去的可执行文件,能够失去以下报告(为了节俭篇幅,只截取报告问题的局部)。 ==12288== Invalid write of size 4==12288== at 0x1091B1: main (main.cpp:9)==12288== Address 0x5b7fc84 is 0 bytes after a block of size 4 alloc'd==12288== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)==12288== by 0x109176: main (main.cpp:7)==12288== Invalid read of size 8==12288== at 0x109C0A: __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >::__normal_iterator(int* const&) (stl_iterator.h:783)==12288== by 0x109819: std::vector<int, std::allocator<int> >::begin() (stl_vector.h:564)==12288== by 0x10C522: Printer<int>::print() (printer.cpp:7)==12288== by 0x109203: main (main.cpp:17)==12288== Address 0x0 is not stack'd, malloc'd or (recently) free'd==12288== Process terminating with default action of signal 11 (SIGSEGV)==12288== Access not within mapped region at address 0x0==12288== at 0x109C0A: __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >::__normal_iterator(int* const&) (stl_iterator.h:783)==12288== by 0x109819: std::vector<int, std::allocator<int> >::begin() (stl_vector.h:564)==12288== by 0x10C522: Printer<int>::print() (printer.cpp:7)==12288== by 0x109203: main (main.cpp:17)报告中次要发现了两个问题,,一个是位于main.cpp:9的内存拜访越界,另一个是位于main.cpp:17的对空指针的成员函数调用。其中第一个谬误是因为在分配内存时,谬误的应用sizeof(),只调配了4字节的容量,却将其误用为大小为100个int大小的内存区域。第二个谬误是因为应用了空指针的成员变量(member,printer.cpp:7)。修改这两个谬误,持续检测该程序。 ...

January 16, 2022 · 2 min · jiezi

关于c++11:理解-stddeclval-和-decltype

std::declval 和 decltype题图来自于 C++ Type Deduction Introduction - hacking C++ 但略有变形以适宜 banner对于 decltypedecltype(expr) 是一个 C++11 新增的关键字,它的作用是将实体或者表达式的类型求进去。 #include <iostream>int main() { int i = 33; decltype(i) j = i * 2; std::cout << j;}它很简略,无需额定解释。 但如此简略的一个货色,怎么就须要新增一个关键字这么大件事呢?还是元编程闹的!元编程世界里,长的狐疑人生的一串模板类申明让人解体,反复书写它们更是累赘。例如一条运行时调试日志输入: 这不是我印象中最长的名称,只是最棘手就能截取的一个援用。这样的例子多的是。 借用我的 谈 C++17 里的 State 模式之二 也即 fsm-cxx 的应用例子稍加改写来体现 decltype 的用途: void test_state_meta() { machine_t<my_state, void, payload_t<my_state>> m; using M = decltype(m); // equals to: using M = machine_t<my_state, void, payload_t<my_state>>; // @formatter:off // states m.state().set(my_state::Initial).as_initial().build(); // ...}显然,using M = decltype(m) 更简练,特地是当 machine_t<my_state, void, payload_t<my_state>> 可能是一超级长带超级多模板参数的定义的字串时,decltype 的价值还会体现的更显著。 ...

October 21, 2021 · 3 min · jiezi

关于c++11:简述C11中auto和deltype

本文是对auto和decltype的简述,更加具体的内容看文章后参考局部。 一. auto和deltype作用C++11规范引入了auto和decltype类型说明符,它能让编译器帮咱们去剖析表达式所属的类型。 auto 的推导能力较弱,而且还有限度。 例如: auto 不能推导数组类型: int arr[5] = {1}; // auto 不能用于推到数组类型。 因为{arr}纯正是一个句法结构。 它不是一个表达式,也没有类型。 因而, auto 不能从中推断出它的类型。 auto auto_arr2[10] = {arr}; // !!!! error: ‘auto_arr2’ declared as array of ‘auto’而 decltype 能够: int arr[5] = {1}; decltype(arr) auto_arr1[5] = {1};除此之外还有其余中央,咱们接着往下看。 总之,decltype的呈现是为了解决auto存在的一些问题,能用auto肯定能够用decltype。然而auto相比decltype又更加简洁,所以能用auto就应用auto,不行就用decltype。 二. decltype 和 auto 区别简略的了解能够说,auto作用变量, decltype作用表达式。 auto类型说明符用编译器计算变量的初始值来推断其类型(用auto申明的变量必须初始化),而decltype尽管也让编译器剖析表达式并失去它的类型,然而不理论计算表达式的值。编译器推断进去的auto类型有时候和初始值的类型并不齐全一样,编译器会适当地扭转后果类型使其更合乎初始化规定。例如,auto个别会疏忽顶层const,而把底层const保留下来。与之相同,decltype会保留变量的顶层const。decltype在解决顶层const和援用的形式与auto有些许不同,如果decltype应用的表达式是一个变量,则decltype返回该变量的类型(包含顶层const和援用在内)。如果表达式的内容是解援用操作,则decltype将失去援用类型。正如咱们所相熟的那样,解援用指针能够失去指针所指对象,而且还能够给这个对象赋值。因而decltype(*p)的后果类型就是int&. 参考C++11新个性— auto 和 decltype 区别和分割C++ auto和decltype的区别C++中关键字auto和decltype的区别C++11:decltype类型推导

September 29, 2021 · 1 min · jiezi

关于c++11:c缓冲池循环队列实现

承接上周工作,目标成果是写线程将数据写入缓冲区,读线程获取缓冲区中的数据。 缓冲区数据结构所谓缓冲区,就是开拓一段内存空间来保留数据,次要包含的属性为贮存数据的内存空间,缓冲区长度,已应用的长度。对应的办法为将数据写入缓冲区,从缓冲区中读入数据,设置已写入的缓冲区长度。所建设的数据结构为: class Buffer {private: USHORT* buffer; // 缓冲区 int maxSize; // 缓冲区最大长度 int effectiveSize; // 曾经应用长度public: Buffer(int bufferSize); // 设置缓冲区大小 void setEffectiveSize(int size); // 设置缓冲区已用数据 void write(std::function<int(USHORT*, int)> const& writeBuffer); // 将数据写入缓冲区 void read(std::function<void(USHORT*, int, int)> const& readBuffer); // 从缓冲区中读取数据 ~Buffer();};这里write办法和read办法承受的参数为c++11定义的lambda表达式。例std::function<int(USHORT*, int)> const& writeBuffer代表传入一个lambda表达式,承受参数为USHORT*, int。 具体实现构造函数实现:将缓冲区大小作为参数传递给构造函数,在构造函数中申请内存空间,并设置相应属性。write和read函数实现将缓冲区数据和缓冲区长度作为参数传入lambda表达式参数并调用。Buffer::Buffer(int size) { this->maxSize = size; this->effectiveSize = 0; this->buffer = new USHORT[size];}void Buffer::setEffectiveSize(int size) { this->effectiveSize = size;}/*** writeBuffer: lambda表达式,承受参数为 USHORT*:缓冲区数据 int:缓冲区最大长度, 返回int:写入数据的长度*/void Buffer::write(std::function<int(USHORT*, int)> const& writeBuffer) { this->effectiveSize = writeBuffer(this->buffer, this->maxSize);}/*** readBuffer: lambda表达式,承受参数为 USHORT*:缓冲区数据 int:缓冲区无效长度 int:缓冲区最大长度 返回void*/void Buffer::read(std::function<void(USHORT*, int, int)> const& readBuffer) { readBuffer(this->buffer, this->effectiveSize, this->maxSize);}对于同一个缓冲区,write和read操作因该是互斥的,否则就会导致数据错乱。因而须要信号量的实现来保障write和read互斥。 ...

May 15, 2021 · 3 min · jiezi

关于c++11:oj学习笔记基础语法入门AB-输入输出练习

warning:纯初学者,代码示例都是本人手写的,只能过oj,有问题虚心接受!输出输出蕴含一系列的a和b对,通过空格隔开。一对a和b占一行。 输入对于输出的每对a和b,你须要顺次输入a、b的和。如对于输出中的第二对a和b,在输入中它们的和应该也在第二行。 样例输出 Copy1 510 20样例输入 Copy630 //#include<iostream>//using namespace std;//int main()//{// int a,b;// while(cin>>a>>b)// {// cout<< a + b <<endl;// }// system("pause");// return 0;//} 输出第一行是一个整数N,示意前面会有N行a和b,通过空格隔开。输入对于输出的每对a和b,你须要在相应的行输入a、b的和。如第二对a和b,对应的和也输入在第二行。样例输出 Copy21 510 20样例输入 Copy630 //#include<iostream>////using namespace std;////int main(){// int i,j,N;// scanf("%d",&N);// while(N--){// scanf("%d %d",&i,&j);// cout << i + j <<endl;// }// return 0;//} 你的工作是计算两个整数的和。输出输出蕴含若干行,每行输出两个整数a和b,由空格分隔。输入对于每组输出,输入a和b的和,每行输入后接一个空行。样例输出 Copy1 510 20样例输入 Copy6 30 //#include<iostream>//using namespace std;//int main()//{// int a,b;// while(cin>>a>>b)// {// if(a == 0&&b == 0){// break;// }else{// cout<< a + b <<endl;// }// }// // system("pause");// return 0;//} ...

April 27, 2021 · 1 min · jiezi

关于c++11:C-专题-右值引用移动语义与完美转发

这三个概念都是c++11引入的概念,在此总结一些浅显的了解。 右值与左值右值是什么(左值)应该在c++98就有左右值的概念吧(不确定我也懒得调研啊)。所谓左值右值,左值取址(地址),右值取值(数值)Lvalue = expressions of which we can get memory addressRvalues = expressions of which we can't get memory address左值refer to 内存中的某一具名对象,比方变量、函数参数、类对象右值次要是长期对象,比方literals(字面值)、操作的长期后果(a+b), 函数非援用返回的长期对象,匿名对象...为什么要有右值以下内容来自某乎某高赞答案,为避免生效,除了链接间接把原文也抄了过去可是实现上为了即使是右值表达式为了求出其值仍然须要调配一块内存来寄存这个值,这造成了一个长期对象,即便之后没有给这个对象取名,对象仍然造成了,仍然经验了结构和析构的残缺生命周期。这是C++反对RAII之后的事件了。而且即使当初根本类型的右值表达式并不需要当时分配内存。左右并非是C/C++才开始有的,而是以后计算机体系结构的客观现实。原本,左值是内存上的变量(有地址),右值是只在寄存器当中存在的值(没有地址)。这样就好了解了吧。只不过有了构造体和对象概念之后,单个数据类型也可能大到无奈残缺寄存在寄存器当中,须要在栈或者堆(反正都是内存)上开额定空间长期寄存。再加上RAII的要求,所以才把事件搞那么绕,那么简单。其实若把这个长期空间(scratch memory)看作寄存器的扩大,不属于惯例内存,那么就好了解了。事实上C++从语法规定上禁止对右值取地址,即使它在内存上有地位。这就是通知你,这片内存并不是通常的内存,逻辑上它只是超大寄存器。https://www.zhihu.com/questio... 右值援用(与左值援用)ok,右值能够了解,那右值援用是什么鬼?为什么要refer一块在内存上然而没法取地址拜访的货色。c++98中的援用很常见了,就是给变量取了个别名,咱们能够通过解援用获取到变量(对象),实际上这具体指的是左值援用

April 7, 2021 · 1 min · jiezi

关于c++11:网易-Duilib功能全面的开源桌面-UI-开发框架

网易 Duilib 框架概述Duilib 是 Windows 零碎下的开源的 DirectUI 界面库(遵循 BSD 协定),完全免费,可用于商业软件开发。 Duilib 能够简略不便地实现大多数界面需要,包含换肤、换色、通明等性能,反对多种图片格式,应用 XML 能够不便地定制窗口,能较好地做到 UI 和逻辑相拆散,尽量减少在代码里创立 UI 控件。目前,Duilib 曾经在国内有较为宽泛的应用。 网易在研发网易易信 PC 版时引入 Duilib,通过多年开发和改良,由网易云信在2019年4月开源。(github 地址:https://github.com/netease-im...) 网易 Duilib 应用 C++11 重写,在其原有根底上做了较大重构,搭配谷歌的根底组件 Base 库、基于 Chromium 的 WebView 框架 CEF 以及罕用的 UI 组件,造成了一套功能强大、简略易用的残缺桌面 UI 开发框架。 网易 Duilib 整体框架整体组件架构框架中提供了多线程模型、高精度定时器、根本的 xml 解析、zip 解压等性能;封装了一层渲染接口和全局款式资源的对立治理;并且对 DPI 适配、多语言、虚构键盘、手写板等性能减少了反对;在下层提供了丰盛的控件。 线程模型和音讯队列开发框架中集成了 Chromium 中 base 库的线程模型和音讯队列,base 中蕴含了多种音讯循环、异步操作接口。网易 Duilib 框架中的 UI 音讯循环、工作线程都齐全依靠 base 的线程模型。应用 base 的异步通信能力,咱们能够将耗时的工作(如资源解析)放到辅助线程来加重 UI 线程的压力。同时,网易 Duilib 中的各种根底组件,都曾经继承了 base 中的生命周期检测能力,每个工作在执行时都会先查看与之绑定的对象是否存活,确保多线程操作不会因野指针而导致解体。理论我的项目开发中,应用base的线程模型,咱们能够非常简单做到 UI 线程、数据库线程、网络线程、其余工作线程之间的通信与交互,无效晋升开发速度。 ...

December 25, 2020 · 2 min · jiezi

C学习笔记4

表达式基础左值和右值左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置) 赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是一个左值 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值 内置解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符的求值结果都是左值 内置类型和迭代器的递增运算符作用于左值运算对象,其潜质版本所得的结果也是左值 算术运算符 算数运算符(左结合律) 运算符功能用法+一元正号+ expr-一元负号- expr*乘法expr * expr/除法expr / expr%求余expr % expr+加法expr + expr-减法expr - expr一元运算符的优先级最高,接下来是乘法和除法,优先级最低的是加法和减法 逻辑和关系运算符 逻辑和关系运算符 结合律运算符功能用法右!逻辑非!expr左<小于expr < expr左<=小于等于expr <= expr左>大于expr > expr左>=大于等于expr >= expr左==相等expr == expr左!=不相等expr != expr左&&逻辑与expr && expr左逻辑或expr expr逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。这种策略称为短路求值 赋值运算符赋值运算符满足右结合律,且优先级较低,优先级低于关系运算符优先级 递增和递减运算符两个版本:前置版本和后置版本前置版本:先将运算对象加1(减1),然后将改变后的对象作为求值结果后置版本:求值结果为运算对象改变前的那个值,之后将运算对象加1(减1)后置运算符的优先级高于解引用运算符 成员访问运算符分为点运算符和箭头运算符,解引用运算符的优先级低于点运算符 条件运算符形式为: cond ? expr1 : expr2;cond是判断条件的表达式,expr1和expr2是两个类型相同或可能转换为某个公共类型的表达式首先执行cond的值,如果条件为真则对expr1求值并返回该值,否则对expr2求值并返回该值条件运算符满足右结合律 cout << ((grade < 60) ? "fail" : "pass"; //输出pass或者failcout << (grade < 60) ? "fail" : "pass"; //输出0或者1cout << grade < 60 ? "fail" : "pass"; //错误:试图比较cout和60位运算符 位运算符(左结合律) 运算符功能用法~位求反~expr<<左移expr1 << expr2>>右移expr1 >> expr2&位与expr & expr^位异或expr ^ expr位或expr expr左移运算符(<<)在右侧插入值为0的二进制位,右移运算符(>>)的行为依赖左侧运算对象的类型,如果该运算对象是无符号类型,在左侧插入值为0的二进制位,如果运算对象是带符号类型,在左侧插入符号位的副本或值为0的二进制位位求反运算中,char类型的运算对象首先提升为int类型,提升时运算对象原来的位保持不变,往高位添加0即可移位运算符满足左结合率 ...

May 26, 2020 · 1 min · jiezi

flutter-线程通信与消息循环C

环境: flutter sdk v1.5.4-hotfix.1@stable 对应 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f 这里关注的是flutter在C++层的线程表示, 没有涉及dart层的线程 线程创建flutter底层(C++)的线程(fml::Thread)是和消息循环紧密关联的,即每一个fml::Thead实例都创建了一个消息循环实例,因此如果要创建一个裸线程是不应该用fml::Thread的。fml::Thread内部即是用C++11的std::thread来持有一个线程对象,参看fml::Thread构造函数(thread.cc:25)。 线程运行体做了2件事 创建消息循环实例并关联线程fml::Thread对象获取消息循环的TaskRunner对象实例并赋值给线程fml::Thread,即线程也持有一个TaskRunner实例这个TaskRunner是个干啥的,还得看的fml::MessageLoop实现fml::Thread的实现非常简单,关键还是看它关联的fml::MessageLoop。 线程存储消息循环fml::MessageLoop首先用了线程存储来保存一个回调,这个回调的作用是显式释放一个fml::MessageLoop内存对象,所以先搞清flutter底层是如何进行线程存储的。 线程存储对象即作用域与线程生命周期一致的存储对象,fml::ThreadLocal即为线程存储类,它要保存的值是一个类型为intptr_t的对象;fml::ThreadLocal在不同平台用了不同的实现方式 类linux平台用了pthread的库函数pthread_key_create来生成一个标识线程的key键,key对应的值是一个辅助类Box,它保存了intptr_t对象和传入的回调方法ThreadLocalDestroyCallback。ThreadLocal使用前需要声明的关键字是static对象析构的顺序稍有点绕, 各对象析构调用序列如下: ThreadLocal::~ThreadLocal() ThreadLocal::Box::~Box() pthread_key_delete(_key) ThreadLocal::ThreadLocalDestroy ThreadLocal::Box::DestroyValue ThreadLocalDestroyCallback() => [](intptr_t value) {} MessageLoop::~MessageLoop() ThreadLocal::Box::~Box()这样看似乎thread_local.cc:27处的delete操作是多余的? windows平台ThreadLocal使用前直接用了C++11标准的关键字thread_local。消息循环消息循环即异步处理模型,在没有消息时阻塞当前线程以节省CPU消耗,否则以轮询的方式空转很浪费CPU资源,消息循环在安卓平台上很常见,其实所有的消息循环都大同小异。 关联线程明白了线程存储,那么在创建fml::Thread对象时调用的MessageLoop::EnsureInitializedForCurrentThread就很浅显了(名字虽然有点累赘),当前线程是否创建了消息循环对象,如果没有那么创建并保存。这样消息循环就与线程关联起来了, 通过什么关联的?tls_message_loop这个线程存储类对象。 消息队列MessageLoopImpl ::delayed_tasks_就是实际的消息队列,它被delayed_tasks_mutex_这个互斥变量保证线程安全。看着有点累赘,其实就是用了一个优先级队列按执行时间点来插入,如果时间点相同就按FIFO的规则来插入。 队列元素是一个内部类DelayedTask, 主要包含消息执行体task和执行时间点target_time,order其实是用来排序的。 循环实现MessageLoop对象构造函数创建了2个重要实例,消息循环实现体MessageLoopImpl和fml::TaskRunner, 而fml::TaskRunner内部又引用了MessageLoopImpl。MessageLoopImpl::Create()创建了不同平台对应的消息循环实现体,于是MessageLoop与MessageLoopImpl之间的关系也非常清楚了: MessageLoop是MessageLoopImpl的壳或者MessageLoopImpl是MessageLoop的代理,MessageLoopImpl是不对外暴露的、与平台相关的、真正实现消息读取与处理的对象。 MessageLoopImpl::Run,Terminate,WakeUp是纯虚函数,由平台实现,譬如安卓平台的实现MessageLoopAndroid调用的是AndroidNDK方法ALooper_pollOnce, MessageLoopLinux调用是Linux阻塞函数epoll_wait。 这里涉及的类和方法有点绕,其实想达到目的很简单:读取并处理消息的操作是统一的,但线程唤醒或者阻塞的方式是允许平台差异的 发送消息一个消息循环关联一个TaskRunner,而TaskRunner细看实现发现全都是MessageLoopImpl的方法,再联系之前在AndroidShellHolder构造函数里创建的TaskHost,就可以发现所谓的TaskRunner无非就是给指定消息循环发送消息,而一个消息循环是和一个线程(fml::Thread)关联的,因而也也就是给指定线程发送消息,没错,正是线程间通信!TaskRunner也正是声明成了线程安全对象(fml::RefCountedThreadSafe<TaskRunner>) 这样其实一切都串联起来了: fml::TaskRunner正如android中的android.os.Handler, fml::closure正如android中的Runnable, fml::TaskRunner不断的将各种fml::closure对象添加到消息队列当中,并设定消息循环在指定的时间点唤醒并执行。 线程结束fml::Thread析构函数调用了自身的Join方法, 这个操作初看有点别扭,后来才明白意图:主调线程需要同步的等待被调线程结束,名称不如Exit来的言简意赅。Join方法先异步发送了一个结束消息循环的请求(MessageLoop::GetCurrent().Terminate()),然后阻塞式等待结束。结合以上列出线程退出的调用序列: Thread::~Thread() Thread::Join() TaskRunner::PostTask()...[异步]MessageLoop::Terminate() MessageLoopImpl::DoTerminate() MessageLoopImpl::Terminate() => MessageLoopAndroid::Terminate() ALooper_wake()...[异步,函数开始返回] MessageLoopImpl::Run() => MessageLoopAndroid::Run() MessageLoopImpl::RunExpiredTasksNow() MessageLoopImpl::DoRun()MessageLoop::Run()...[异步]ThreadLocal::~ThreadLocal()[省略,同线程存储对象析构的调用序列]线程体系回看AndroidShellHolder的构造函数,其中涉及flutter::ThreadHost, fml::TaskRunner, flutter::TaskRunners,在创建Shell对象之前还创建了一系列线程:ui_thread, gpu_thread, io_thread,并对TaskRunner有一系列操作,有点杂乱但现在看其实就非常清晰了。 ...

July 4, 2019 · 1 min · jiezi

基于C可变参数模板格式化字符串

遨游于C++世界时,最讨厌的当属于对c-style的兼容????。 在格式化字符串时,通常使用的是snprintf这个c函数。snprintf是sprintf的安全版,能够避免缓冲区溢出。 char buf[1024] = {0};std::string s = "Hello world";snprintf(buf, sizeof(buf), "format str: %s", s.c_str());snprintf接受的参数跟printf差不多,都是c-style的数据类型,如%s接受的是const char*类型的数据,这就需要我们将std::string做一个转换。这一个小小的转换让我觉得非常不爽,有没有可能让std::string在做某个函数的参数时能自动做转换?甚至让一个将一个普通的对象自动转换成const char*类型? 接下来我们将利用c++11的可变参数模板(variadic templates)、参数包(parameters pack)、完美转发(perfect forwarding)等特性来实现这一想法。 参数列表完美转发写C++代码时,我满脑子都是怎么最大限度地提高性能。我们这次的目标也一样,在提供方便的同时,不要对性能有太大影响,甚至不影响。 首先是要将传入fmt函数的参数完美转发至snprintf。 template<typename... Args>string fmt(const char *format, Args&&... args) { char buf[128] = {0}; snprintf(buf, sizeof(buf), format, convert(std::forward<Args>(args))...); return buf;}这是一个可变参数模板,args表示传入的参数们。我们的思路是将传入的参数做一个转换之后传给snprintf。 为了原封不动的保持左右值引用,首先是用Args&&代替Args的参数类型,此处模板函数的Args需要编译器推导,所以是一个通用引用(Universal reference),可以指代左值或右值。用std::forward<Args>能保持参数的左右值性质,做到参数的完美转发。 自动参数转换在convert这个函数中,我们要将特定的类型转换成const char*类型,而那些能被snprintf接受的类型如int, double, char*,则原封不动的返回。 convert函数针对不同的参数类型需要返回不同的类型。这里也将返回值作为一个模板类型即可。 template<typename T>struct item_return { using type = T&&;};convert函数的定义为: template<typename T>inline typename item_return<T>::type convert(T&& arg) { return static_cast<T&&>(arg);}convert函数默认将传入的参数原封不动的返回。接下来我们要做模板的偏特化,对于指定的对象,将其转换为const char *类型 ...

May 30, 2019 · 1 min · jiezi

C++ Tips

读了《C++ 的门门道道 | 技术头条》这篇文章之后有很多共鸣,可以说是近期看过的最好的技术 tips 文章了。按照这篇文章里面讲到的几点,结合工作上实际遇到的问题,我也来说一下我的感受。<!– more –>成员变量初始化成员变量忘了初始化是一个相当经典的错误,甚至《Effective C++》中还专门列了一条来讲这个事情。在工作中,我就看到过这种错误,同事对一个新增的功能加上了开关控制的逻辑,但是忘了对这个开关的标识进行初始化,导致了一条分支逻辑失效。而且因为 C++ 没有默认初始值,那它的初始值是随机的,所以导致线上的表现是概率性复现,增加了 debug 的难度。当然增加 Coverity 扫描提早发现这个问题,当时项目上线比较急就直接跳过了这一步。从 C++11 开始支持在声明成员变量的时候直接初始化,有了这个特性之后,我已经养成了所有成员变量都直接在声明的时候初始化。class Ad {private: unsinged int lifetime = 10000;};sort() 里的坑这个坑即便是有点经验的程序员也会踩到,有一次线上事故就是一个稳定跑了很久的逻辑,突然出现了 core,而且是持续地 core 在 sort 上。花了很长时间排查,最后才意识到实现新增的 sort 比较函数没有保证严格弱序(strict weak order),比较两个对象的属性时用了 <=。这里就涉及到 C++ 中 sort 的实现。细节之后会写一篇文章来讲,简单说来就是 STL sort 核心排序算法是快排,在依据 pivot 调整元素位置时采用的实现方式如下:while (true){ while (__comp(*__first, __pivot)) ++__first; –__last; while (__comp(__pivot, __last)) –__last; if (!(__first < __last)) return __first; std::iter_swap(__first, __last); ++__first;}重点就在于 while (__comp(__first, __pivot)) ++__first;,当整个容器里的元素都相等时,就会导致 __first 这个迭代器越界,程序就 core 了。操作符短路原文关于如何避免操作符短路讲得很好了,其实我们还可以利用操作符短路来简化代码。对我更常用的场景:if (!stack.empty() && stack.top() == 0),这恰恰是利用短路来合并判断。别让循环停不下来这个有个经典场景,在 vector 里面,我要找到首个递增序列的最后一个元素,很容易写成这样的代码:while (i < ve.size() - 1 && ve[i] <= ve[i + 1]) i++;这里如果传入的 ve 是个空 vector,那么就会成为超大循环,因为 vector::size() 返回的是 unsigned int,根据数值类型传递,ve.size() - 1 的类型也是 unsigned int,那么就会返回一个很大的数,导致 while 陷入超大循环。理解 vector 的实现vector 可以说是在日常开发中使用频率最高的容器了,支持下标访问,动态扩容,二分查找的效率,C++11 之后支持移动构造,这些优点都让它非常好用。vector 的坑都集中在它的动态扩容上,理解它动态扩容的机制可以在开发中避开这些坑。vector 动态扩容的两个特点:vector 扩容是按照 2 的指数倍往上翻的,也就是 2, 4, 8, 64, 128, ……。动态扩容时是会全量复制一遍现有的所有元素到新分配的内存中。根据这两个特点,结合 vector 的其他特性得到的:尽量预先分配好 vector 的空间,使用 reserve() 预分配空间,避免多次扩容。不要在 vector 里存储大对象,扩容的时候会全量复制,额外的性能开销很大。不要保存指向 vector 内部对象的指针,扩容时对象地址会发生变化。reserve() 是提前分配空间,此时不能直接用下标索引访问(如果用基本类型倒是能访问,但是这种行为仍然是未定义的)。有时候真的不必用 std::unorder_map组里有一个项目升级到 C++11 之后,一窝蜂地使用 unordered_map,但是其实对于小数据量,比如本次请求命中的一些配置,其实数据量基本都在 10 项以内,那其实用 map 就完全够用了,unorder_map 查找的效率当然是高的,但是也要认识到它维护一个哈希表额外付出的性能代价。慎用short,char因为一开始设计的数值类型过于严格而导致的重写,我遇到过不止一次了。有些人写代码的时候有一种倾向,就是能省则省,能用 int 的绝不用 long,能用 short 的绝不用 int。但是其实有些情况下 short 并不能节省空间(字节对齐),还导致过度「优化」,导致逻辑变化之后要重写,或者实际的取值不符合设计导致溢出。避免箭头型代码什么是「箭头型代码」?见下图:这种代码其实在业务复杂的场景下并不少见,酷壳上有一篇文件专门讲过如何重构这种代码:《如何重构“箭头型”代码》。我在实际项目中应用比较多是利用 while (0) 来规避这种代码。在项目经常遇到的场景是对一连串条件进行判断,不符合条件的分支需要打印日志,示例代码如下:if (conditionA()) { if (conditionB()) { if (conditionC()) { if (conditionD()) { // do something } else { // log } } else { // log } } else { // log }} else { // log}这种情况下用 do-while(0) 可以进行非常好的重构,重构之后的代码如下:do { if (!conditionA()) { // log break; } if (!conditionB()) { // log break; } if (!conditionC()) { // log break; } if (!conditionD()) { // log break; }} while (0);参考文章:《C++ 的门门道道 | 技术头条》《STL源码剖析》 ...

March 11, 2019 · 2 min · jiezi