Boost的contract库
看一篇文章, 里面提到契约式编程, 说在C++标准还没有明确下来之前, 可以利用已有的库, 想起来Boost里面似乎有一个, 但是我从来没有关心过. 觉得还是不要这么孤陋寡闻的好, 于是打开找找, 果然找到了.
Qt对象的内存优化
当你的自定义类需要利用Qt的容器类存储时, 可以对类做更精细的指定, 从而指导Qt使用更合适的内存管理算法.
Qt里面有一个宏, Q_DECALARE_TYPEINFO, 它的原型为: Q_DECLARE_TYPEINFO(Type, Flags). 其中, Type是要设置的类, 而Flags可以取下面的值:
Q_PRIMITIVE_TYPE, 它的意思是这个类的行为类似于基本类型, 可以通过简单地零初始化内存来创建, 也不需要有销毁操作, 并且可以用memcpy()来创建副本.
参考std::is_trivial的定义, 它的意思大概也是一样的, 指的是它要么是基本类型, 要么是没有定义函数的POD.
Q_RELOCATABLE_TYPE, 表示这个类虽然有构造函数和/或析构函数, 但是利用memcpy在内存中移动.
Q_MOVABLE_TYPE: 同Q_RELOCATABLE_TYPE, Qt更建议使用Q_RELOCATABLE_TYPE. 并且, 这里的MOVABLE和
Q_COMPLEX_TYPE, 这个是缺省值. 它表示这个类的实例不能在内存中 ...
Lambda拾遗
Lambda拾遗Lambda的状态一般来说, 我们会说当需要保持状态时, 我们会使用函数对象来实现, 这样写很直观. 但是同样可以使用lambda实现某些特别的效果. 例如下面的代码:
12345678910void NormalTester::test_case2(){ int x = 1; auto fun1 = [x]()mutable { TRACE() << "执行结果: " << ++x; }; TRACE() << "执行前: " << TSHOW(x); fun1(); fun1(); fun1(); TRACE() << "执行后: " << TSHOW(x);}
注意, 这里必须要在fun1定义为mutable, 否则编译会不过. 输出为:
12345QDEBUG : NormalTester::test_case2() [ NormalTester: ...
Day 15 低倍图区域选择界面
低倍图选区界面我们回过头来继续完善单张扫描的界面。今天我们要处理一下低倍图的选区实现。从功能上讲,低倍图的选区和预览图的选区是很相似的,都是在一个对话框上面拖动一个区域来设置要选择的范围。但是这里要更复杂一些,因为低倍图会扫描几十,上百,甚至上千张——如果使用10倍镜对一张涂满了的玻片做扫描,我们会得到大约1300张照片。当然,一般情况下根本不需要使用这么多,对于一般的样本来说,包含两百个白细胞的外周血区域其实也就大约火柴贵粗细的范围。通常情况下,通过对拍摄得到的预览图的识别处理,只需要拍摄一百左右的低倍图就够了。只有很特殊的情况,比如癌症晚期白细胞特别稀疏,核型,尤其是用户有特别要求才需要扫描整张玻片。
扫描的视野数量多了主要的问题是占用内存资源的大小问题。一张彩色的2448x2048分辨率的照片,图像数据大约会占用15M,1000张照片就是15G内存。对大多数使用环境来说,这是一个很客观的消耗,而且还是一个小概率使用场景。如果是大概率需求,那么事情反而很简单——只要堆资源就行了。反而是小概率事件不好这么做。我们会从易到难,先实现一个支持图像数量适度的实现,然后再考虑如何进一步让它能 ...
string-view, span, range辨析
在某种成都上,string-view,span和range::views在功能上有所相同之处。他们都是对某个连续内存区域的映射,同时并不owner对象。它们的区别主要是在于使用的意图以及功能的多寡上面。
string-view是一个std::string的只读的视图。从某种意义上,我们理解为std::string::c_str的一个面向对象的readonly的封装吧。差不多就是这个意思。
std::span要更通用一些,它不再局限于std::string了。可以只想其他的对象。并且,它即可以是只读的,也可以是读写的。span<T>和span<const T>的区别。另外,在C++23中还有std::mdspan。多维而已,其他的和span在理念上是相通的。
range::views,就更广泛了。但是它的核心思想是lazy evaluation,而且它提供了filter,transform等一票函数,这才是它的重点。
按照我的理解,string-view也好,span也好,应该是传统的指针的替代物,更多的是作为函数的参数使用的,避免数据拷贝的成本;而ra ...
标准库range笔记
以前使用range-v3, C++21中增加了range的支持, 但是感觉差异还是蛮大的. 慢慢收集总结一下.
使用std::range和range-v3的差别使用range-v3,最简单的方式是#include <range/v3/all.hpp>,把所有的头文件都放进去就是了。编译速度慢就慢了,大不了扔到预编译头文件里面去。使用std::range,好像标准库没有给出一个合集,所以需要自己一点点加进去。目前,ranges和algorithm是至少需要的。
range-v3和std::range的对比-views大家都有的:
all
cartesian_product
chunk, chunk_by
concat
counted
common
drop, drop_while
empty
enumerate
filter
iota,range-v3的ints也比较接近
keys, values
join
repeat
reverse
single
split
stride
take, take_while
transform
zip
to
—std::range有的
...
optional,expected,variant做返回值的比较
optional, expected, variant的比较从C++17开始,C++标准中陆续引入了一些表示“替代”意义的数据类型。包括C++17中引入的std::optional,std::variant,以及C++23引入的std::expected。当然,C++17中还引入了std::any,这个和我们讨论的内容距离有点远,就不讨论了。
提到这个问题,是因为在项目中遇到一个设计选择问题,我该如何设计函数的返回值体系,如何让代码看起来更自然易懂,流程更清晰明确。
这里就还牵涉到另一个数据类型,在boost::Leaf,我们会放到一起讨论。
首先看std::optional,它代表一个“可以为空”的值。这里的“空”,从逻辑意义上,表示的是一个没有意义,或者无效的值,最典型的是当表示指针时的空指针。在之前,我们都是使用某个具有特殊意义的值来表示。例如用0表示空指针,用-1表示无效的取值(当合理的值范围为自然数时)等。但是这种方式并不直观,有时也不好用——比如,取值范围为整个有理数区间时,你可能会为找一个“合法的无效值”而头痛。甚至,最简单的,到底是0表示成功还是非0表示成功,就够令人头 ...
Day 14 串口命令
基于串口通信的自动送片系统实现在前面,我们演示了如何实现手工单片扫描的功能实现。在现实中,这种工作模式是无法提高什么工作效率的,甚至还不如医生自己拿到显微镜下去人工数。只有实现整个流程的自动化才能真正体现出系统的效率。因此,我们需要为扫描仪装上自动上片系统。
自动上片系统的软硬件简介抛去硬件实现的细节不谈,所谓自动上片系统,本质上就是单片机控制的一个机械手,它实现将玻片从片仓中取出放到载物台上,以及从载物台取走玻片放回片仓中的操作——核心就是这两个操作。它有一个很孱弱的单片机,控制步进电机运动,同时和主机通信。通信协议一般是基于串口的二进制消息。一般来说,这种系统的二进制消息定义也十分简单。比如我们使用的上片机,其接口定义如下:
下行消息(上位机->下位机)
123456789字段 长度 值 说明TAG 2 0xEB90 LEN 1 0x00-0xFF 从DIR到CRC的长度DIR 1 取值: 0x00: 下行 0x01: 上行 CMD ...
Day 13 选区对话框
接下来讨论一下在预览图上确认低倍扫描范围的界面实现。和参数确认相比,这个对话框的实现技术要复杂的多,其他细节也麻烦的多。而同时,相对于后面要介绍的在低倍图上确认高倍扫描的界面,又要简单很多,起着一个承前启后的作用。
程序结构调整在开始开发之前,我们发现一个很烦恼的事情,就是我们要测试CellScanner项目中的界面会很麻烦。因为它是一个exe,我们无法直接引用,必须在测试项目中使用源码,从而要修改一堆的路径,还很容易出错。
接下来,我们会再新建一个DLL子项目,把新的界面组件也都放在这里面。这一步应该是轻车熟路了。新的库工程参数如下图所示:
然后,把它添加到CellScanner项目中。
我们最终的目标是把CellScanner中的所有的类都挪过来,在CellScanner中只留下一个main.c就可以了。暂时嘛,先维持现状不变。
创建目录的时候,建议在项目目录下再建立一个目录,把源码都放进去。比如,我们在项目目录下又建立了一个目录cellwidgets,这样,在其他项目中,可以这样使用:#include "cellwidgets/previewchoosedial ...
Day 12 实现简单界面
实现ControlPanelFormControlPanelForm用于显示界面左侧的信息栏,它有两部分组成:上面主要是一个QLabel,用于显示样本的预览图,下方是一个QTableWidget,用于显示扫描进度。是一个很粗糙的界面设计,但是用于演示也够了。
它包含四个slot函数,我们会依次简单看一下:
显示预览图信息函数onSigPreviewCaptured()用于在收到sigPreviewed后显示样本信息。它将收到的cv::Mat转换成QPixmap,并按照这个QLabel的实际大小做缩放,并设置到QLabel上面。
12345678910void ControlPanelForm::onSigPreviewCaptured(cv::Mat prev_mat, cv::Mat label_mat, const QString &qrcode){ _impl->_prevMat = prev_mat.clone(); _impl->_qrcode = qrcode; auto pixmap = ImageTool::MatToQP ...