本文总结在平时开发工作中一些杂项零碎知识点。
SVN操作
改名操作
- 先把该文件从工程中排除掉
- 利用svn自身的Rename功能对文件进行重命名
- 然后将改名后的文件重新加入到VS工程中去
移动文件
- 从svn中,将想要移动的文件通过鼠标右键选中并且移动,在目的目录选择拷贝到这里
- 从VS项目的文件里面中,将此文件删除
- 然后在VS项目的目的目录中,将此文件重新添加进去
更新失败处理
若在文件正在被占用时执行svn update
,svn会提示更新失败,此时整个svn状态不正确,右键点击目录无正确的入口菜单,可以通过如下方法来解决:
- svn是采用sqlite3来管理数据的,因此需要下载 sqlite3 ,并将可执行文件路径添加到系统的环境变量中
- 进入到 .svn的同级目录,然后执行
- sqlite3 .svn/wc.db "select * from work_queue" ---> 看到很多记录
sqlite3 .svn/wc.db "delete from work_queue" ---> 删除这些更新失败的记录
sqlite3 .svn/wc.db "select * from work_queue" ---> 确认是否删除完毕 - 在此运行 svn 的 clean up
经过如上操作,就能清除更新失败的文件,再重新update一下。
查看文件版本信息
-
查看项目目录的右键属性
-
查看项目修订记录中的记录,当前标记为黑色的为当前项目的版本号
JIRA问题单号需要和SVN的提交编号绑定起来,那样在查找问题时,方便双向回溯查找。
问题搜索
看书的时候,带着问题和自己预期的答案去在书中查找对应的知识点,或印证,或加强,或反思。对于好的东西,我们不光要学习,而且要把它内化为自己的东西。我们要学习别人的先进经验,办事方法,但在运用中要实事求是,具体问题具体分析。
以寻找解决"获取操作系统所在磁盘分区信息"方案为例子,在刚开始时,我犯了一个错误具体表现为,没有将注意力关注于问题域,而是在网上大范围的搜索获取磁盘信息做法,在找到的这些通用做法中,不仅会有需要的内容,也有大量不需要关系的内容。在面对这些内容,本着多学一点是一点的心态,开始写些demo来学习,但做到后面,越发觉得距离最原始的问题越来越远。就算把整个相关知识点全部吃透,再回到原始问题上,花费的精力相当于杀鸡用牛刀。虽说最终还是解决了问题,但从时间和效率上,还有大大的优化的空间。
反思下,以后在针对问题进行方案搜索时,要先定义问题域范围并且了解基本背景知识。然后,针对待解决的问题,只搜索关联度高的网页,大致浏览一片,若觉的合适,则可写些测试代码,这里的测试不要掺杂与待解决问题无关的代码,只关心待解决的问题,能简则简。
效率提高点
在进行会议讨论(需求讨论、方案论证等)时,就讨论的问题,陈述的中心思路是讲解问题的背景、问题的现状、问题的困难点,而不是讲述自己对该问题心中默认的结论,我们讨论的是问题,而不是问题提出者的结论。
工作用到的IDE,最多只能打开两个,在一个IDE中完成一个任务后,如果没有其他的任务,要关闭它。防止在太多IDE中切换,耗费无用时间,就像进程调度一样,进程切换的成本很高,无论是机器还是人,都要一段时间才能进入全速运行状态。
删除Dll时提示被占用
在 Windows 下面删除dll时,提示被系统使用,无法删除。较好的思考路径是:
1. 找到该 dll 被哪个进程占用?
Windows下可以使用 tasklist /m XXX.dll 来找
2. 在任务管理器中,暂时中止该进程(如果是窗口管理器占用,那么中止后,任务栏会消失)
3. 通过 dos 命令来删除文件和文件夹(删除文件使用 del, 删除空文件夹使用 rd)
代码小技巧
针对动态空间的,若已知空间大小,可以使用
int nReqSize = 1024;
string strBuf(nReqSize, ' ');
char* pReqBuf = (char*)strBuf.c_str();
do...while(false)
,配合break可实现类似goto
的功能,用于简化多级判断代码的跳出处理。
do{
if (condition1)
break;
if (condition2)
break;
}while(0)
在一系列计算的中间过程,只要有一处临时变量在计算过程中,可能超过相关数据类型能够表达的最大范围,那么最终结果就很可能会出错,这种错误很难发现,要满足特定的场景才会发现,在后续涉及到较大数量的计算时,一定要注意。
利用静态代码检查工具cppcheck
,cpplint
来辅助检查你的代码。针对资源泄露的问题,注意相关资源申请和释放函数的配对使用
调试经验
-
在需要查看数据发送内容,而又不想通过断点来中断执行流程,可以通过 VS 的 条件断点 配合特定的条件,在调试输出窗口中查看收发内容。
-
找内存泄漏:根据vld输出的信息,找到泄露内存的具体字节数和内存分配序号,然后在 dbgheap.c 文件中的 391 行出 打上条件断点,通过申请的具体字节数来大致定位是哪一个文件上申请了内容,可以大致定位内存泄漏的源头。
-
描述类与类,窗体与窗体之间的相互交互的关系,最好用序列图来描述。流程图中异步处理,可通过带箭头的虚线来表示异步响应的流程。
-
永远不要混合Tab键和Space键。 VS设置,将TAB键转换为4个空格
-
在程序中,全角和半角英文混用,容易造成显示不一致,在设计的时候要注意。
-
在工程实践中,多线程开始初始化之前,对整个工程中所涉及到的单例按照前后的逻辑顺序,完成手动的初始化,这可避免首次调用时的不确定性,简单明确。
-
当一个条件判断(if,while)比较复杂,并且是隐含有一定的业务背景知识的,一定要写好注释,不仅是给自己方便,也是给其他人方便.
-
从接口中返回const char*指针给外部的string变量,所对应的一块内存是如何响应外部变化的?实际上是调用
string::operator=(const _Elem* _Ptr)
赋值构造函数来完成对应地址内容的拷贝的. -
提示:在VS调试,无法进入到stl的内部函数中去,因为用到 破解的VS助手,把VS助手给禁用掉,重新启动VS,就可以调试进入stl内部。
-
发布给别人的软件,一定要先在自己虚拟机上面跑一次,防止一些软件依赖的dll在用户机器上没有,启动时爆出“未找到 mfc100.dll”的问题。多在自己本地测试下,确保在各种情况下正确后,再交付给用户。
-
C++本身提供了四种标准异常: 只能通过 传值 或者 引用 来捕获, 传值捕获异常,会将异常对象拷贝两次,效率较低。
bad_alloc --> operator new 不能分配足够内存时抛出
bad_cast --> 当 dynamic_cast 针对一个引用reference操作失败时,抛出
bad_typeid --> 当 dynamic_cast 对空指针进行操作时,抛出
bad_exception --> 用于 unexcepted 异常总结:优先使用传引用来传递异常
-
对象构造要做到线程安全,要求在构造函数期间,不对外暴露this指针。作为class数据成员的mutex,只能用于同步本class的其他数据成员的读和写,它不能安全的保护析构.因为类成员的生命周期最多与对象一样长,而析构动作发生在对象销毁之时。
二进制兼容性
二进制兼容性指的是在升级库文件时,不必重新编译使用了这个库的可执行文件或者其他库文件,程序的功能不被破坏。
C++二进制兼容性,在于以虚函数作为库接口时,更新库接口后调用的不确定性。本质在于虚函数是以vtable[offset]
方式实现虚函数调用,offset
又是根据虚函数声明的位置隐式确定,一旦新旧版本的虚函数相对位置有变化,就可能带来兼容性问题。
解决方法:
-
可以使用 静态链接
-
用 pimpl 技法: 在头文件中只暴露 non-virtual 接口,并且class的大小固定为 sizeof(Impl*),这样可以随意更新库文件而不影响可执行文件。
在什么场景下,需要考虑二进制兼容性? 当你是一个shared library的维护者,你提供的库被其他团队、其他公司、其他社区所使用时,需要考虑。这里面有两点:共享库的维护者,并且该库被不受你控制的团队所使用。
如果只是开发团队内部特定场景下特定功能的库,使用范围较窄可以不用太在意二进制兼容性,发布新版本(修复Bug或者增加功能),通知外部使用者重新编译,使用即可。
修补Bug的patch应该做到二进制兼容,比如1.1.1应该和1.1.0,兼容,修复了一些bug,或者增加不影响二进制兼容的功能如果新增功能与原有版本的二进制不兼容,那就应该发布到1.3.0版本上去。
如果需要hot fix,那么一定要发布为动态库,否则,在分布式系统中使用静态库更容易部署。
作为一个库来说,以什么方式暴露库的接口?一般来说有以下三种方式:
-
以全局(含namespace级别)的函数为接口
-
以class的non-virtual成员函数为接口,内部辅以 pimpl(pointer to implementation) 来提供具体实现。<-- 推荐采用这种方式,可以确保二进制兼容性
并考虑多采用 non-member,non-firend function in namespace 作为接口,对外暴露的接口不要有虚函数,不要有数据成员。 -
以class的virtual函数为接口(interface),客户端继承这个interface,然后将对象实例注册到库中,由库来回调自己
Debug和Release库兼容性
- 在Windows上,比如Visual C++编译的时候要选择Debug或者Release模式,而且Debug模式编译出来的library通常不能在Release binary中使用,反之亦然。这是因为在这两种模式下的CRT二进制不兼容,主要是内存分配方面的,Debug有自己的堆。
在linux下,编译时涉及到调试的选项为 -g,增加调试开关宏-DDEBUG
,编译发布版本时,一般加上 -O3
,然后利用strip
,去掉调试信息.
从这上面来看,linux下的库是不区分Debug模式和Release模式,只不过是运行效率的不同而已。
配置文件实时更新读取
要做到修改配置文件能够实时生效,要做到两点:
-
当配置文件被外部修改时,要主动向外投递"配置已改动"的消息,相关进程要被动。
-
本模块针对获得配置文件数据的数据时,在本地不维护配置属性副本,每次使用时,主动从配置文件接口中去读取。
字体配置
比如,目前A处和B处目前显示字体都是微软雅黑字体ID,现在来需求了:要求B处显示为微软雅黑加粗字体,怎么做比较好?
方案一:每一个字体ID都在配置文件里,若要显示为对应加粗字体,那就新增一个字体配置。如果存在多种字体风格配置文件,那么在每个配置文件中都需要添加该新增字体。
优点:现有获取字体逻辑不变,新增字体对应新增字体ID而已。
缺点:每新增一个与现有字体样式风格差别很小的字体,都要在众多配置文件中新增。
方案二: 基础默认字体这三五种,在不新增字体种类的情况下,提供一个 GetBoldFont(nFondId, BoldFont)
接口函数,可将基础字体显示风格微调,有其他属性,可以增加其他微调接口。
优点:在不新增字体种类的情况下,增加灵活性,如果有其他风格类型的字体需求,直接使用该函数即可。
缺点:在获得加粗字体,需要使用特定的函数来实现。
个人认为,方案二是较好的方式。基础数据提供一套,微调接口提供一套,外部可任意组合实现自定义需求。
界面数据请求
当有一个界面,需要请求数据A,数据B。又有一个新界面,也要请求数据A,数据B,再来了一个新界面,也要请求数据A,数据B。如何减少重复请求代码?
总体思想:将各个界面重复性的请求代码逻辑抽离出来,抽离出来放在哪里呢?
有两个选择,一是放到基类中去,二是放置在独立实现中。
方案一会在基类业务中混杂具体的业务数据请求逻辑,本来基类应该是业务无关的,加上去会混淆职责,以后有新的通用数据需求,可能还会增加到基类中,长此以往,基类会越来越庞大,不利于维护。
方案二,将通用的操作放到独立数据收发类中去,对外提供统一接口,具体来说,各类数据的请求接口是类似的,细分需求可通过传参来区分。不同界面对数据的响应处理是互不相同的,因此,响应的处理要抽离成虚函数,留给各个界面去处理。各个业务界面继承该数据收发类,重载响应虚函数即可。
方案三:将通用的操作放到独立数据收发类中去,对外提供统一接口,但是,数据收发类和具体的业务类之间是通过注入的方式完成关联,每个请求关联一个发送请求页面的指针,当数据返回时,请响应数据回调给发送请求页面即可。
个人认为,方案二和方案三都有适用场景,需要具体问题具体分析。