0

编译、链接、调试

目前工作方向发生了挺大的变化,现在已经从Researcher慢慢过渡到了Developer。现在主要是负责一些SDK相关的设计和开发工作,最常使用到的就是C/C++。众所周知,C/C++是最复杂,最强大的一套编程工具,当然其中的概念和坑也是十分的多。这一系列的文章主要是记录我对编译、链接、动静态库、符号表、ABI、以及调试等的理解。

关于编译链接等的知识,可以参考这本经典的参考书:《程序员的自我修养——链接、装载与库》。不过呢,如果只是阅读而不实践的话,很多的知识很难记住。但一般也只需要了解相关的概念,在遇到问题的时候能够想到坑的突破点,其实就已经足够了。 read more

0

TICTOC: Header Only C++ Timer

感觉最近的更新频率略高啊~哈哈~

这次的带来的是一个十分简单便利的C++计时库。

项目地址:https://github.com/miaoerduo/tictoc 欢迎Start和提MR

项目中有详细的说明和Demo,可以很直观的体验到这个库的易用性。

先看一下效果,如果我们正确使用的话,大致会出现类似下面的信息:

可以显示,我们的每个区域的代码(包括行号)的消耗时间。精确到微秒。

起因是这样的,之前有很长时间的工作内容是优化一些特定的函数,保证新旧的SDK的速度的对齐。然后C++虽然有一些工具可以分析运行状态,但通常还是简单的打印时间来的方便 /* Print大法好 */ 。之后,和工程的小伙伴一起Debug的时候,就发现他写了一个头文件,然后用绝对路径的方式去include,而头文件里面就是各种常用的小工具,而最常用到的就是时间的打印。 read more

0

C++ Boost JSON解析库的使用

最近在写一个C++项目的时候,有大量的配置信息,于是将这些配置信息整合进一个文本文件中,选择了JSON这种数据格式。C++在处理JSON数据的库有很多,比如Jsoncpp,Boost等,这个项目中由于本身就已经用到了Boost这个库,因此,也就选用Boost来进行JSON的解析了。

Boost的JSON解析,使用的是property_tree这个数据类型,它可以方便的解析XML和JSON。

一、Boost JSON解析库的几个注意事项

在具体介绍之前,必须要强调一下,这个库默认不是线程安全的!不是线程安全的!不是线程安全的!不做任何处理的情况下,如果直接在多线程的程序中使用Boost解析JSON,可能会在奇怪的时候报段错误。

这是由于Boost的JSON解析是基于SPIRIT语法解析的,而SPIRIT本身就不是线程安全的,我们如果需要它支持线程安全,就必须加入一个宏#define BOOST_SPIRIT_THREADSAFE,把它放在引用boost的头文件的最开始就行。理论上,在编译的时候加入宏也是可以的。

另一个需要注意的是,一般网上找的教程中,property_tree都是不支持unicode编码的,如果想要支持unicode,需要一些额外的操作。这个从网上可以查到,我尝试了一下,最终还是放弃了。取而代之的一个方案就是把中文的各种路径啥的,用软链接替换成英文和数字。之后世界就美好了。

二、boost::property_tree::ptree 类型

对于JSON或者XML,boost将他们解析之后都会生成一个ptree的数据结构。类似于下面的结构。

可以看出,这是一个很标准的树的结构。对于树中的每一个节点,都有自己的数值和子节点,每个子节点都有一个唯一的名字。data_type和key_type通常是std::string或std::wstring。如果希望处理unicode的字符串的话,就需要用到std::wstring了。下面的例子中,使用的全部都是std::string。

三、JSON文件的解析

首先,我们用一个小栗子,来介绍一下Boost是如何读取JSON数据的。

这里首先我们需要定义一个boost::property_tree::ptree类型的对象,之后通过boost::property_tree::read_json函数进行数据的读取,之后就可以使用各种ptree的接口进行数据的操作了。
在boost/property_tree/json_parser.hpp文件中我们可以看到读写JSON的一些接口。

它支持读写JSON,对于读取操作,它支持直接根据文件名称来加载JSON或者通过输入流来加载。输出也是相同。所以我们上面的Demo中,需要将字符串s转换成字符串流对象ss,之后才能进行加载。写文件支持写入到文件或者输出流中,最后一个bool值表示是否格式化输出json。

四、JSON对象的读取

我们知道JSON对象主要有两种格式:键值对和数组。JSON灵活就在于键值对的值还可以键值对或者数组,数组的每个元素也是。

那么我们分别介绍键值对和数组的数据获取方式。

1)键值对的解析

ptree支持一个操作叫做get_child,可以根据键的名字,来获取子节点。而且这个名字还可以是累加的。什么叫可以累加呢?我们看一下下面的代码:

输出的结果为:

get_child这个函数,可以根据节点的名字,获取到子节点的ptree对象。这个节点的名字可以使用.连接各个层级的名称。get_value<Type>方法,可以获取节点的值,并且转换成期望的数据类型。如果我们就是想获取节点的值。不期望有任何转换,可以使用data这个函数。

get_child要求输入的名称路径必须是存在的,否则会抛异常。如果我们不知道某个名称路径是否存在的话,可以使用get_child_optional这个函数,如果路径不存在,该函数会返回boost::null。get_child_optional返回的类似于指针的结构,如果需要获取值,可以用这样的写法:pt.get_child_optional("some_key")->get_value<int>()。

我们可以向现在这样通过各种树的操作,选择到我们的需要的节点,再通过get_value<Type>函数获取到数据值。但这样的操作有时候会有点繁琐。boost支持更简化的一些操作。下面是同样功能的一个例子:

get这个函数相当于先get_child得到要找的节点,之后再调用get_value<Type>这个函数。get_value<Type>这个函数可以获取节点的值,同时把它转换成Type格式。即ptree.get<int>("a.b")等价于ptree.get_child("a.b").get_value<int>()。

通过get函数,我们可以很方便的获取某个节点的数据,而且还能顺便完成类型的转换,真的不能更方便了!

2)数组的解析

为什么数组的解析要单独来说呢?因为,数组格式中,没有键,所以我们不能根据名字来获取节点了,所以读取的方式有了些许的不同。

Boost针对数组,给我们提供了遍历子节点的迭代器接口。可以十分方便的遍历某节点的所有的子节点(当然在键值对的解析中也可以使用)。

打印的结果:

可以看出,Boost中将JSON数组也是按照键值对的方式去存储,只是键的内容是一个空的字符串。迭代器的first是键的结果,数组中就是空字符串。second就是我们的值。

3) 其他的实用接口

bool empty(): 返回该节点是否含有子节点。比如当一个节点已经是叶子节点的时候,可以用这个函数来判断。
assoc_iterator find(const key_type &key): 给定一个名字路径,返回指向该节点的迭代器或者boost::property_tree::ptree::not_found。
size_type count(const key_type &key): 返回指定名称路径的节点的子节点的数目。

五、JSON对象的编辑

Boost支持很多的对JSON对象的写的操作,但是我在项目中没有用到,所以在这里暂时就没有动力整理下去了~~ 这里附上Boost ptree的文档,方便大家查阅:

https://www.boost.org/doc/libs/1_65_1/boost/property_tree/ptree.hpp

六、疑难杂症

1.怎么判断某个键是否存在?

使用get_child_optional,再判断返回是否为boost::null,这个对象直接相当于false。

2.怎么方便的遍历数组?

这个功能,我还专门查过。其实懂了之前的迭代器的使用,就能方便的遍历了。下面是我用的一个代码。

使用的话就这样:

不过这个解决方案有个问题,就是如果根节点就是数组的话,似乎就不能很好的work了。

3.怎么解析中文

/(ㄒoㄒ)/~~

转载请注明出处,谢谢!

0

C++ 并发队列的原理简介与开源库concurrentqueue安利

由于最近在做一个项目,但是框架本身有个不合理的设计。其中的代码是单线程的,数据的读取和计算都在一个线程里面完成。也就是说,我们的程序有很大的一部分时间在读取文件数据,导致最终的运行速度很慢。这里就可以使用多线程来优化。

这里需要使用最基本的生产者消费者模式

使用若干个线程作为生产者,负责数据的读取和预处理,这部分任务是IO密集型的,也就是不太占CPU,但是比较占带宽,而且有延时。在处理完数据之后,将数据放到一个队列中。

同时,使用若干个线程充当消费者,从这个队列里面获取数据,然后进行计算。计算的部分是CPU密集型的(其实我这里计算是GPU做的,就只有一个消费者),计算完成之后输出结果。

那么贯穿这一整套方案的,就是我们的队列。

在并发任务中,通常都需要一个队列机制,将并行的任务转化成串行的任务,或者将串行的任务提供给并行工作的线程。这个队列会同时被多个线程读写,因此也必须是线程安全的。

一、线程安全的实现策略

对于线程安全的队列的实现,似乎经常成为企业的面试题,常见的实现方法就是互斥量和条件变量,本质上就是锁的机制。同一时间只有一个线程具有读写的权限。锁的机制在并发量不大情况下,十分的清晰有效。在并发量较大的时候,会因为对锁的竞争而越发不高效。同时,锁本身也需要维护一定的资源,也需要消耗性能。

这时候,大家肯定会想问,不使用锁机制,还可以处理这种并发的情况吗?

答案是肯定的,首先我们知道锁主要有两种,悲观锁和乐观锁。对于悲观锁,它永远会假定最糟糕的情况,就像我们上面说到的互斥机制,每次我们都假定会有其他的线程和我们竞争资源,因此必须要先拿到锁,之后才放心的进行我们的操作,这就使得争夺锁成为了我们每次操作的第一步。乐观锁则不同,乐观锁假定在很多情况下,资源都不需要竞争,因此可以直接进行读写,但是如果碰巧出现了多线程同时操控数据的情况,那么就多试几次,直到成功(也可以设置重试的次数)。

我们生活的时候,总会碰到很多的不顺心的事情,比如模型训练崩了,被某些库搞得头大,或者女票又生气了什么的,不妨学习一下乐观锁的精神,再训一次?再编译一次?大不了再哄一次。一次不行就两次。

回到乐观锁上,乐观锁中,每次读写都不考虑锁的存在,那么他是如何知道自己这次操作和其他线程是冲突的呢?这就是Lock-free队列的关键——原子操作。原子操作可以保证一次操作在执行的过程中不会被其他线程打断,因此在多线程程序中也不需要同步操作。在C++的STL中其实也提供了atomic这个库,可以保证多线程在操控同一个变量的时候,即使不加锁也能保证起最终结果的正确性。而我们乐观锁需要的一个原子操作就是CAS(Compare And Swap),绝大多数的CPU都支持这个操作。

CAS操作的定义如下(STL中的一个):

首先函数会将obj与expected的内容作比较:

  1. 如果相等,那么将交换obj和val的值,并返回true。
  2. 如果不相等,则什么也不做,之后返回false。

那么使用这个奇怪的操作,为什么就可以实现乐观锁了呢?这里我们看一个例子。这也是我学习的时候看的例子。

在我们向list中插入元素的时候,首先获取到当前的头指针的值head,然后我们在写数据的时候,首先和此刻的头指针值作对比,如果相同,那么就把新的节点插入。如果不相同,说明有线程先我们一步成功了,那么我们就多尝试一次,直到写入成功。

以上就是使用CAS操作实现的乐观锁。上面的这个append就是最简单的Lock-free且线程安全的操作。

二、concurrentqueue

最近在做这个项目的时候,就被安利了一个header only的C++并发队列库 concurrentqueue。本着不重复造轮子的原则,我在项目中用了这个库,由于它只是两个头文件,特别方便的就加入到了项目中。关于这个库的特点,项目的github上写了很多。这里直接照搬下来,不做解释。

  • Knock-your-socks-off blazing fast performance.
  • Single-header implementation. Just drop it in your project.
  • Fully thread-safe lock-free queue. Use concurrently from any number of threads.
  • C++11 implementation — elements are moved (instead of copied) where possible.
  • Templated, obviating the need to deal exclusively with pointers — memory is managed for you.
  • No artificial limitations on element types or maximum count.
  • Memory can be allocated once up-front, or dynamically as needed.
  • Fully portable (no assembly; all is done through standard C++11 primitives).
  • Supports super-fast bulk operations.
  • Includes a low-overhead blocking version (BlockingConcurrentQueue).
  • Exception safe.
  • read more

    0

    [转载] GDB十分钟教程

    GDB十分钟教程

    作者: liigo
    原文链接: 
    http://blog.csdn.net/liigo/archive/2006/01/17/582231.aspx
    日期: 2006年1月16日

    本文写给主要工作在Windows操作系统下而又需要开发一些跨平台软件的程序员朋友,以及程序爱好者。

    GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。

    GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。 read more

    0

    [转载] 应用Valgrind发现Linux程序的内存问题

    原文地址:https://www.ibm.com/developerworks/cn/linux/l-cn-valgrind/

    Valgrind 概述

    体系结构

    Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。Valgrind的体系结构如下图所示:

    图 1 Valgrind 体系结构

    Valgrind 体系结构

    Valgrind包括如下一些工具:

    1. Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。这也是本文将重点介绍的部分。
    2. Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
    3. Cachegrind。它主要用来检查程序中缓存使用出现的问题。
    4. Helgrind。它主要用来检查多线程程序中出现的竞争问题。
    5. Massif。它主要用来检查程序中堆栈使用中出现的问题。
    6. Extension。可以利用core提供的功能,自己编写特定的内存调试工具。

    Linux 程序内存空间布局

    要发现Linux下的内存问题,首先一定要知道在Linux下,内存是如何被分配的?下图展示了一个典型的Linux C程序内存空间布局:

    图 2: 典型内存空间布局 read more

    40

    dlib人脸关键点检测的模型分析与压缩

    人脸关键点检测的技术在很多领域上都有应用,首先是人脸识别,常见的人脸算法其实都会有一步,就是把人脸的图像进行对齐,而这个对齐就是通过关键点实现的,因此关于人脸关键点检测的论文也常叫face alignment,也就是人脸对齐。另一方面,对于美颜,2D/3D建模等等也需要一来人脸的关键点技术,而且通常也要求有尽可能多的人脸关键点。

    Dlib is a modern C++ toolkit containing machine learning algorithms and tools for creating complex software in C++ to solve real world problems. It is used in both industry and academia in a wide range of domains including robotics, embedded devices, mobile phones, and large high performance computing environments. Dlib’s open source licensing allows you to use it in any application, free of charge.

    Dlib是一个包含了大量的机器学习和复杂软件开发工具的现代C++工具箱,被广泛的用于软件开发等领域。

    本篇博客主要研究的就是Dlib中的人脸关键点检测的工具。该工具的方法依据是 One Millisecond Face Alignment with an Ensemble of Regression Trees by Vahid Kazemi and Josephine Sullivan, CVPR 2014 这篇论文,在速度和精度上均达到了极好的效果。

    本文的侧重点在于人脸关键点模型的存储结构的分析和模型的压缩策略分析,最终在性能几乎不变的情况下,得到模型的至少10倍的压缩比。项目最终的github地址为:https://github.com/miaoerduo/dlib-face-landmark-compression 欢迎fork、star和pr。

    注意:

  • 本文假定了读者对该论文有一定的了解,可以使用Dlib完成人脸关键点的训练和部署,因此不做论文的相关方法的解释。
  • 本文中分析的数据都是Dlib的shape_predictor类的私有成员,这里不得不把他们的修饰符从private改成了public,但文中并没有专门指出。
  • 本文中所有的代码均在本地的64位操作系统上运行,在变量数据存储的大小描述的时候也均以64位来说明,即使是不同的编译器也会对数据大小造成影响,但这不是本文的重点。
  • 本文中的数据类型如果不在C++中见到的数据类型,则为下面的typedef的数据类型:
  • read more

    0

    C++ Lambda表达式

    小喵的唠叨话: 寒假之后,小喵在家里无所事事,最近用C++写代码的时候,用到了std::sort这个函数,每次用这个函数,小喵似乎都得查一下lambda表达式的写法。正好最近很闲,不如总结一下。

    在Bing上搜索C++ lambda,第一条记录就是MSDN上的C++ lambda的介绍。本文也是基于这篇文章来写的。

    那么接下来,我们分几个部分来介绍。

    一、什么是Lambda表达式

    MSDN上对lambda表达式的解释:

    在 C++ 11 中,lambda 表达式(通常称为 “lambda”)是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法。 Lambda 通常用于封装传递给算法或异步方法的少量代码行。[1]

    看了这个解释,相信大家已经理解lambda表达式是什么。简而言之,lambda表达式就是一种定义函数的简单的方法。

    举一个简单的例子:求一个数的阶乘。

    这是一般的函数的写法:

    Lambda表达式的写法:

    乍一看,这两种定义方式十分的相似。但其实这是两种完全不同的方式,前一种是函数定义式,而后一种是一个表达式。factorial是变量名,等于号后面的是值,也就是一个lambda表达式,本质上是一个匿名的函数。最终factorial就是一个函数。

    很多时候,我们只是直接书写lambda表达式,而不需要给他一个名字。比如排序的时候,sort可以接受一个自定义的比较函数,这时候直接书写lambda表达式即可。

    二、Lambda表达式的作用

    由于lambda本身其实也就是一种函数的定义方式。因此它的主要作用还是和一般函数一样。但是lambda表达式相对于一般函数,又有一些功能之外的作用。参考了知乎上的一些回答[2],小喵也进行了总结。

    1、可以用表达式来定义函数,这样使得函数的定义和调用在一起,语意和逻辑上更为紧凑。同时,对于只是用一次的短小的函数,直接调用匿名的lambda表达式是最好的选择,这样就不需要给每个函数起名字了。/* 起名字一直是一个很令人头疼的问题 */

    2、闭包(Closure)。这个小喵的写javascript的时候时常会用到。闭包本质上就是能够访问上下文环境中变量的代码块。

    这里我们简单的举个例子,还是之前的求阶乘的问题,现在我们有些提高需求。

    现在需要完成下面的三种阶乘的运算:

    n! = n * (n – 1) * (n – 2) * …

    n!! = n * (n – 2) * (n – 4) * …

    n!!! = n * (n – 3) * (n – 6) * …

    要求编写3个函数,分别完成上述3种计算。

    使用一般的方式写很容易实现,我们这里直接使用lambda表达式来实现:

    编译的时候要注意,lambda表达式是C++11开始支持的,所以需要指定一下C++的版本。

    运行之后的结果为:

    这里作为返回值的lambda表达式,可以访问先前传入的参数,这也就是闭包。具体的语法,我们后面会讲到。

    3、柯里化(Currying)。这部分小喵也是第一次接触,维基百科有如下解释:

    在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。[3]

    下面给出一个例子(也是实现之前的阶乘):

    4、lambda表达式整体可以被当做函数的参数或者返回值。

    闭包和currying的例子就是将整个lambda表达式作为返回值。现在再举一个作为参数的例子:

    运行的结果:

    其实函数也可以当参数传入的(函数指针),但是lambda表达式要更为直观和灵活一些。谁能一眼看出int (*func(int))(int)究竟是什么意思呢(这是一个函数的定义,输入的参数是int,返回值是一个函数指针,函数指针对应的函数的输入和输出类型都是int)。

    三、Lambda表达式的语法

    看到前面的lambda表达式的各种有趣的功能,现在是不是非常迫切的想尝试一把?

    ISO C++ 标准展示了作为第三个参数传递给 std::sort() 函数的简单 lambda:

    lambda表达式的组成部分见下图:

    1. Capture 子句(在 C++ 规范中也称为 lambda 引导。)
    2. 参数列表(可选)。 (也称为 lambda 声明符)
    3. 可变规范(可选)。
    4. 异常规范(可选)。
    5. 尾随返回类型(可选)。
    6. “lambda 体”

    接下来我们需要学习这6个部分。

    1、Capture 子句

    我们知道,一般情况下,函数只能访问自己的参数和外部的全局变量。而lambda表达式却可以访问上下文的变量(参见闭包的例子)。那么如何指定要访问的变量,以及访问的方式(值或者引用)呢?这就是Capture 子句要解决的问题。

    Lambda 可在其主体中引入新的变量(用 C++14),它还可以访问(或“捕获”)周边范围内的变量。 Lambda 以 Capture 子句(标准语法中的 lambda 引导)开头,它指定要捕获的变量以及是通过值还是引用进行捕获。 有与号 (&) 前缀的变量通过引用访问,没有该前缀的变量通过值访问。

    空 capture 子句 [ ] 指示 lambda 表达式的主体不访问封闭范围中的变量。

    可以使用默认捕获模式(标准语法中的 capture-default)来指示如何捕获 lambda 中引用的任何外部变量:[&] 表示通过引用捕获引用的所有变量,而 [=] 表示通过值捕获它们。 可以使用默认捕获模式,然后为特定变量显式指定相反的模式。 例如,如果 lambda 体通过引用访问外部变量 total 并通过值访问外部变量 factor,则以下 capture 子句等效:

    我们之前的闭包中使用的就是通过值访问。

    使用 capture-default 时,只有 lambda 中提及的变量才会被捕获。

    如果 capture 子句包含 capture-default &,则该 capture 子句的 identifier 中没有任何 capture 可采用 & identifier 形式。 同样,如果 capture 子句包含 capture-default =,则该 capture 子句的 capture 不能采用 = identifier 形式。 identifier 或 this 在 capture 子句中出现的次数不能超过一次。 以下代码片段给出了一些示例。

    capture 后跟省略号是包扩展,如以下可变参数模板[4]示例中所示:

    要在类方法的正文中使用 lambda 表达式,需要将this指针传递给 Capture 子句,以提供对封闭类的方法和数据成员的访问权限。

    这里大家可能觉得有点奇怪,将this指针传给Capture子句?

    其实我们常使用的成员函数也是用类似的方法实现的。我们知道,使用成员函数需要有一个类实例,但是调用类函数就不需要。这是因为成员函数的第一个参数是this,当然这个参数我们编写代码的时候不需要自己手动写出,而是默认的。使用像python这样的语言的时候就是需要显示的写出的。在使用类实例调用成员函数的时候,会默认将this指针传入。成员函数有这么一个参数,就可以访问类实例的各种变量和方法。而类函数是没有这个参数的,也就是没有this这个指针,因此它的调用并不需要类实例,当然也就不能访问类实例的变量。

    在使用 capture 子句时,要记住以下几点(尤其是使用采取多线程的 lambda 时):

    1. 引用捕获可用于修改外部变量,而值捕获却不能实现此操作。 (mutable允许修改副本,而不能修改原始项。)
    2. 引用捕获会反映外部变量的更新,而值捕获却不会反映。
    3. 引用捕获引入生存期依赖项,而值捕获却没有生存期依赖项。 当 lambda 以异步方式运行时,这一点尤其重要。 如果在异步 lambda 中通过引用捕获本地变量,该本地变量将很可能在 lambda 运行时消失,从而导致运行时访问冲突。

    通用捕获 (C++14)

    在 C++14 中,可在 Capture 子句中引入并初始化新的变量,而无需使这些变量存在于 lambda 函数的封闭范围内。 初始化可以任何任意表达式表示;且将从该表达式生成的类型推导新变量的类型。 此功能的一个好处是,在 C++14 中,可从周边范围捕获只移动的变量(例如 std::unique_ptr)并在 lambda 中使用它们。

    2、参数列表

    除了捕获变量,lambda 还可接受输入参数。 参数列表(在标准语法中称为 lambda 声明符)是可选的,它在大多数方面类似于函数的参数列表。

    在 C++14 中,如果参数类型是泛型,则可以使用 auto 关键字作为类型说明符。 这将告知编译器将函数调用运算符创建为模板。 参数列表中的每个 auto 实例等效于一个不同的类型参数。

    lambda 表达式可以将另一个 lambda 表达式作为其参数。

    由于参数列表是可选的,因此在不将参数传递到 lambda 表达式,并且其 lambda-declarator: 不包含 exception-specification、trailing-return-type 或 mutable 的情况下,可以省略空括号。

    3、可变规范

    通常,lambda 的函数调用运算符为 const-by-value,但对 mutable 关键字的使用可将其取消。 它不会生成可变的数据成员。 利用可变规范,lambda 表达式的主体可以修改通过值捕获的变量。 本文后面的一些示例将显示如何使用 mutable。

    输出的结果是:

    可以看出n确实是通过值来访问,在lambda1中,我们运行++n,在编译的时候会报错。使用mutable修饰之后,就可以修改参数(副本)的值。

    4、异常规范

    你可以使用 throw() 异常规范来指示 lambda 表达式不会引发任何异常。与普通函数一样,如果 lambda 表达式声明 C4297 异常规范且 lambda 体引发异常,Visual C++ 编译器将生成警告 throw(),如下所示:

    在MSDN的异常规范[5]中,明确指出异常规范是在 C++11 中弃用的 C++ 语言功能。因此这里不建议大家使用。

    5、返回类型

    将自动推导 lambda 表达式的返回类型。 无需使用 auto 关键字,除非指定尾随返回类型。trailing-return-type 类似于普通方法或函数的返回类型部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->。

    如果 lambda 体仅包含一个返回语句或其表达式不返回值,则可以省略 lambda 表达式的返回类型部分。 如果 lambda 体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会将返回类型推导为 void。

    typeinfo的功能是获取一个变量的类型,由于它的实现依赖于编译器,所以在不同平台下的输出可能不完全一样。小喵这边的输出是:

    可以看出,三个lambda的输出是不相同的。默认情况下,会返回一个最直接的类型。

    6、lambda体

    lambda体其实和函数体几乎完全相同。

    lambda 表达式的 lambda 体(标准语法中的 compound-statement)可包含普通方法或函数的主体可包含的任何内容。 普通函数和 lambda 表达式的主体均可访问以下变量类型:

  • 从封闭范围捕获变量,如前所述(Capture)。
  • 参数
  • 本地声明变量
  • 类数据成员(在类内部声明并且捕获 this 时)
  • 具有静态存储持续时间的任何变量(例如,全局变量)
  • read more

    0

    一个简单粗暴的人脸认证标注工具的实现

    小喵的唠叨话:话说最近小喵也要开始写论文了,想了两周还是没有头绪,不知道该写些什么。恰好又被分配了一点标注数据的工作,于是乎想写点代码,休闲一下。结果也就是这篇博客。对了,小喵对GUI编程一窍不通,只知道Windows有MFC,Mac上的不知道。。。恰好听说过QT,而且知道这个界面库是跨平台的,也就选用了这个工具了。

     

    那么现在开始和小喵一起瞎猫似的捯饬QT吧~

    先看一眼效果图:

    是不是乍一看还挺炫酷。功能上也还好,至少简单的标注工作都能完成了。那么让我们来一步一步的完成这个工具吧。

    一、功能需求

    这个程序主要的功能是完成一个人脸认证的标注工具。

    具体来说,就是给定很多对人脸的图片,要标注一下这一对是不是同一个人。同时,每一对的图片的人脸一张是生活照,一张是证件照,需要同时标注出哪张是证件照,那张是生活照。照片都是经过检测和对齐的,这个工具只需要完成简单的显示、标注、保存记录的工作就可以。

    当然考虑到有时候需要标注的list可能很大,可以加入跳转的功能。标注结果都保存在内存,用户可以随时更改,点击保存,则写入硬盘。

    二、数据结构

    那么是不是现在就可以动手写代码了呢?当然不是!

    小喵写这个软件一共用了3天的时间,第一天完成了一个超简单demo程序,熟悉了一下QT的事件添加,路径选择和显示图片的几个功能。之后又仔细的思考了一下各种数据的结构,才动手做了这一版工具。没有一个清晰的数据的概念,会造成许多的无用功。所以,大家在写程序的时候,要在准备阶段多花一点时间来思考,毕竟写代码才是最简单的事情不是吗?

  • 输入数据格式:因为小喵的工作环境下,大家都对linux有一些了解,所以可以自行生成好图片的路径的list,这里统一要求,list必须是偶数行(2n行),代表n对,相邻的图片为一对。
  • 标注数据存储:考虑到我们不仅需要标注是不是一对,还得标注哪张是证件照,所以不妨直接在读数据的时候就分成两份,这样就用两个std::vector<std::string>来存储就行了。
  • 标注过程的状态:我们需要知道标注过程中的那些信息呢?主要应该有:总数据量,当前已标注的对数。
  • 标注结果:每一对都有一组对应地 结果,考虑到有4中情况:未标注,不确定,不匹配,匹配这四种,我们定义一个枚举的状态表enum AnnoState就好。之后用一个std::vector<enum AnnoState>来存储标注结果。
  • read more

    0

    应用OpenMP的一个简单的设计模式

      小喵的唠叨话:最近很久没写博客了,一是因为之前写的LSoftmax后馈一直没有成功,所以在等作者的源码。二是最近没什么想写的东西。前两天,在预处理图片的时候,发现处理200w张图片,跑了一晚上也才处理完一半。早上的时候,出于无奈,花半小时改写了一个简单调用OpenMP的处理程序,用了30个核心,然后一小时不到就处理完了。感慨在多核的时代,即使是简单的程序,如果能支持多核,应该都能节省不少时间。

    本文系原创,转载请注明出处~

    一、写在前面

    对于OpenMP,小喵其实并不是了解很多,而且小喵本身也只用到了OpenMP的最简单的功能。在这里主要是分享一个自己常用的写简单的并行程序的思路。希望能帮助到大家。

    这个设计模式的主要特点如下:

    1,处理的任务是独立的; read more