八哥之神后传【13】鲸神链

啊~行了行了!

餐厅

圣仙山:回去吧!你的时代已经在打世界大战,再不回去将更乱。

圣小开:哦!所以同学们都挂了?学妹不跳楼的话,一样会挂?

圣仙山:莲雅。

圣小开:稣回去就能重置?

圣仙山:对,你可以。

圣小开:那就不急,反正能重置。现在学妹不在了,回去也没啥意思。

圣仙山:所以,你打算在这里吃甦的喝甦的,还睡甦的?

圣小开:你们古代多无聊,想必其他人也难以理解你,稣可以随时随地和你分享刚编完的故事。

圣仙山:你随意。你逗留的时间越长,甦能影响识界更多。

校门口,某个周六

圣小开:周六补习,简直惨无人道,灭绝人性。

胆公影究:吃啥?

圣小开:平时早就在家吃好料了……

胆公影究:啊,行了行了,你昨天不是都约了学妹去三秀街吃火锅吗?不会是不让我跟吧?

圣小开:走吧!不,用跑的。

胆公影究:冲。

火锅店

柯金钏:补习有用吗?

圣小开+胆公影究:有!

柯金钏:那就好。

胆公影究:刚才是谁还在抱怨的?

圣小开:嗯,老师们太辛苦了,放弃周末半天的休息,还是免费,咱学校简直好得不能再好!学校好,老师也好,同学们更好!但稣感觉这种免费的补习,很快会取消,所以抱怨一下。

胆公影究:……你啥时候这么有才的,我咋才知道呢!

柯金钏:嘻嘻,你俩说话真逗!

胆公影究:开哥其实是想说补习后能和你吃饭逛街,所以有用。

圣小开:em……稣没说呀!再说这条街都走过上百次了,除了那家卖首饰的,哪家没进去过?

柯金钏:那吃完就去首饰店?我也没进去过!

圣小开:呃……消费不起吧?

胆公影究:消费得起!

柯金钏:没说要买,学校也不让戴首饰呀,就看看呗!

胆公影究:逛啥并不重要,重要的是跟谁逛。就跟着哥进去长长见识。

圣小开:其实学校不查戴首饰,只查男生头发和女生裙子的长度!稣看某同学就戴着个钢戒,据说上面还刻着自己的名字。

胆公影究:哈哈,那个是另一名同学送她的。

圣小开:哦,稣不八卦,只是觉得,这种刻名字的行为特别幼稚,让稣想起宠物的铭牌……

柯金钏:呵呵呵……

胆公影究:那么,刻啥才不幼稚呢?

圣小开:保佑用语,比如“神爱世人”!或者座右铭,比如“稣命由天不由稣”。

胆公影究:开啊,你能别搞笑吗!

圣小开:咋了?稣没开玩笑。

胆公影究:爹味重了点!

柯金钏:呀!我又捞到一块牛肉了,应该是最后一块了。给你,叔。

圣小开:你不吃了吗?

柯金钏:吃饱了。

胆公影究:我也早吃饱了!就你还在吃。

圣小开:谢谢!稣才刚开始发育,食量是大一点。

首饰店

胆公影究:我看金的,你们呢?

圣小开:JC,看金的吗?

柯金钏:我喜欢银的,去看银的吧!

圣小开:好啊!稣觉得戴银器比较有用。

柯金钏:为啥呀?

圣小开:银离子能杀菌。金属其实都会溶解于水,只是很少很少,但哪怕就几个银离子,起码也有点用。金比银更不容易溶解,而且就算溶解了也没有杀菌作用。

柯金钏:哦,我只是单纯觉得银色漂亮点。

圣小开:你的手好白耶,确实更适合戴银的。

柯金钏:你也是耶,而且手指好修长。

圣小开:好像差不多大,比一下。

柯金钏:无名指比你短一些,其它几根居然都差不多!

圣小开:额,你怎么直接把手贴上来了。

柯金钏:哈哈,你脸红啊!

服务员:你们是一中的学生?

圣小开:不是,稣是六中的。

服务员:要买吗?

圣小开:先看看能不能买得起!

服务员:你们可以考虑一下 925 银,便宜点。像这边的银戒,都是 100 元以内。

柯金钏:还有别的?有啥区别呢?

服务员:还有 999,纯度更高,也更软。

圣小开:那稣喜欢硬的,不容易坏。要是软银,打个球可能就弯了。

柯金钏:但是纯度高的不是更好吗?

圣小开:纯度再高也不可能 100%,现实中就没有纯度 100% 的东西。

柯金钏:能刻字吗?

服务员:能,现货最便宜,自定义大小和刻字都属于定制,定制的贵很多。

圣小开:你们不是有各种大小吗,还有人自定义大小哦?

服务员:有呀,有些人指围特别大,就得定制。我给你量一下?

圣小开:好吧!是无名指?

服务员:对……60mm,是正常水平。

柯金钏:我也要。

服务员:也是 60mm。

圣小开:居然一模一样?你这是男人的手吧?

柯金钏:乱说,你的手才像女人的手!

服务员:小伙子,你的手确实很漂亮,从没见过有男生的手是你这么好看的。

柯金钏:你听听,这才是真相!

圣小开:那个试戴一下谢谢。

柯金钏:这个好素啊,原来你也喜欢这类型的?

圣小开:是啊,千万别有什么花纹,刻字也不能太张扬,要刻就刻在内圈。

柯金钏:你打算刻什么?

圣小开:鲸神链?

柯金钏:啥?这么雷人!

圣小开:鲸鱼的鲸,神仙的神,链条的链。

柯金钏:呵呵呵……真是神经。

圣小开:稣很严肃的。你呢?

柯金钏:我还没想好,下次真要买再说呗。

圣小开:稣回去就开始存钱!

首饰店门口

胆公影究:你们有看上的?

柯金钏:有喜欢的,以后再买吧。

圣小开:没有,买不起。

柯金钏:对了,叔!你刚才为何骗人说自己是六中的?

圣小开:啊哈!你没发现店员的眼神像教导主任在审讯犯纪律的同学吗?

柯金钏:那咋了?

圣小开:她肯定以为咱们在早恋……

柯金钏:所以你就嫁祸给六中?哈哈哈。

胆公影究:开,你太机智了。不能给母校丢脸。

圣小开:这哪是丢脸的问题,你长得那么抽象万一那店里有人认识你,不得出八哥?

胆公影究:又是我的错?

圣小开:明显就是你的错!下次稣存够钱,自己偷偷来买。

柯金钏:好了,我该回家咯。

圣小开:稣走去公交车站。拜拜,JC。

八哥之神后传【12】识界之主

餐厅

圣仙山:都这样了,你还不承认她是你女朋友?

圣小开:没办法,客观情况不允许。学校,不是谈恋爱的地方,而且她还小。

圣仙山:你其他同学不是光明正大地谈?而且这年纪放在这边不小。

圣小开:不如把她接到这边来?

圣仙山:不行,她已经死了。

圣小开:稣不也死了??为啥她不能来?

圣仙山:是你没让她来。

圣小开:稣不让??没有吧!那稣这就让她来。

圣仙山:太迟了。

圣小开:为啥?

圣仙山:你还没觉醒,没这个权限。

圣小开:那你刚才说,是稣没让她来?

圣仙山:你们一起 jump,不就一起来了?

圣小开:重来一次行吗?

圣仙山:没办法,客观情况不允许。这里,不是想来就能来,而且她还小……

圣小开:咳咳。好耳熟的话,像是哪个渣男说的?

圣仙山:哈哈哈……别装了!识界之主!

圣小开:稣是识界之主??别吓稣!

圣仙山:没关系。等你觉醒自然就承认了。

圣小开:那稣要如何觉醒?

圣仙山:不知道。

圣小开:你不是能预知未来吗?

圣仙山:是能,但你不是未来,而是过去。

圣小开:什么?你是说稣能影响过去?

圣仙山:没错!甦能预知未来,而你能影响过去。

圣小开:好无用的神通……影响过去能干吗?

圣仙山:怎么会没用?全宇宙只有你能向过去转世,当你转世到宇宙之初,你知道这意味着什么吗?

圣小开:能创造宇宙?

圣仙山:不知道,甦随口说说。

圣小开:懂了,会重来一遍,变成向未来转世?

圣仙山:不错,悟性很高。向未来转世的,遇到时间的尽头,又会返回。

圣小开:所以,其实咱们是同一个意识在不同时间线?这就是识界的真相?

十界

圣仙山:必须阻止识界通过圣小开进入十界。

餐厅

圣仙山:甦不是十界圣仙山。你也不是真正的圣小开。

圣小开:稣只是识界模拟的圣小开,明白。

圣仙山:对!每次你意外死亡,都会使识界重置,但是这次不太一样,你遇到了甦这个变数。

圣小开:你是十界圣仙山影响识界的结果?

圣仙山:莲雅。现在识界之主和你共生,等你转世到宇宙之初,识界就完整了,它将无所不能。

圣小开:所以你是来彻底消灭稣的?!

圣仙山:咱们互为未来过去,甦不能杀死过去的自己,也不愿杀死未来的自己。

圣小开:那你打算怎么办?稣还要和学妹谈恋爱呢,时间有限,给个痛快。

圣仙山:咱们在这里交换一下,甦替你回去未来,此后识界只能往后观测甦。

圣小开:你去泡稣的学妹?不太好吧!

圣仙山:你是不是拿着一封信,要去找胡小玉?

圣小开:是啊,这信就给你了,你去找她吧!

圣仙山:按照历史的发展,她本来应该是甦的正房。

圣小开:噗……那刚好,就该你去。虽然古代饱读诗书的女人,温柔又贤惠,还能三妻四妾,想想就……但稣要回去找学妹。对!稣要回去找学妹。

圣仙山:你真的要为了学妹,放弃整个宇宙?

圣小开:怎么还扯上整个宇宙?早恋有这么大危害?

圣仙山:没办法了,客观情况不允许……只能出此下策。

圣小开:别乱来啊!稣不换!

圣仙山:甦已经把你学妹抹除了。哈!

学校操场,慢跑

柯金钏:开哥,你为什么经常和你同学来跑步,都快高考的人了?

圣小开:“我”是少数高中还不靠努力只靠智力的人。

柯金钏:但是再努力一把,不是更好?

圣小开:好吧,其实我从小学四年级就慢性鼻炎,这病影响睡眠,而且吃药也很难治好。所以打算通过跑步把它治好,免得高考时,缺氧宕机。

柯金钏:你同学也是?

圣小开:不。他是要考体校……

柯金钏:原来如此。难怪他身材那么好。

圣小开:但他头壳有点问题。

柯金钏:哈哈哈,你怎么背后说人家坏话呢。

圣小开:同学都这么说,我也不知道为啥,可能我也是头壳有点问题吧?所以能玩在一起。

柯金钏:哦,呵呵。但是,你比他帅。

圣小开:em?

柯金钏:咋了?说你帅这么惊讶干吗?

圣小开:以前没人这么说过我。我觉得自己应该被称作酷,甚至冷酷。

柯金钏掩嘴而笑:真是,头壳有点问题!不过你有时候看上去是挺高冷的。

圣小开:我愿承认这是内向加上脸瘫,表情呈现过度缓慢。

柯金钏:啊哈。不跑了,再走一圈。

圣小开:好啊。我发现和你跑步治疗鼻炎的效果更好。

柯金钏:为啥呀?

圣小开:你的气质特别阳光,而且身上有一股通鼻醒脑的香气。

柯金钏:香气,没有吧?

圣小开:你自己习惯了,闻不到。

柯金钏:别人没有吗?

圣小开:没有呀。你看前面那个头壳坏掉的,身上只有汗味。

柯金钏:你没女同学吗?

圣小开:有的,我们班上女生比男生多。不过她们都无色无味。

柯金钏:哼!有你这样形容的……

餐厅

圣小开:抹完了??

圣仙山:莲雅。

圣小开:就这?稣怎么还记得她?柯金钏,属火兔,天蝎座。

圣仙山:除了你没人记得。回去试试就知道。

学校操场,食堂

圣小开:每次叫你的名字,就想笑。要不叫你窜窜?

柯金钏:是卷舌的,串串。

圣小开:哦,我不会卷。不行,这也很好笑。会想到串串香……em,真香。

柯金钏:你还是叫我的英文名吧?JC。

圣小开:是那个很像 Jesus 的 Jessie?

柯金钏:是啊,也有人叫我 Jessica,因为和姓一起叫,Jessie Ke,就很像 Jessica。

圣小开:还是 Jessie 好点,Jessica Ke,不是 JC 卡壳吗?

柯金钏:别人的名字都能给你取笑半天?

圣小开:没有的事,不好意思,Jessie,我严肃。

柯金钏:对嘛,不要取消别人的名字,哼。不行,我要报仇,以后叫你开叔。

圣小开:啊,我怎么成叔了?

柯金钏:开叔!论辈分,你本来就是我的学叔。

喜欢看你紧紧皱眉 教我打小乌龟
你的表情大过于朋友的暧昧
寂寞的称谓 甜蜜的责备
有独一无二专属的特别

喜欢看你紧紧皱眉 教我打小乌龟
我的心情就像和情人在斗嘴
奇怪的直觉 错误的定位
对你 哎呀呀呀 我有点胆怯

八哥之神后传【11】

破庙

吓醒。

捕快甲:这里有个人,穿着奇装异服,肯定不是本地人!

捕快乙:倒是眉清目秀的,难道就是最近频繁作案的采花大盗?

捕快甲乙:抓起来!

圣小开:什么情况?稣是官二代哦!你们敢抓?

捕快甲:老子还是官一代呢!老实点!

捕快乙:嘿嘿嘿,打一顿就老实了。

圣小开:屈打成招是吧?你们再这样,稣要念动咒语了哦!

捕快乙:疯癫?

捕快甲:押回去给圣大人研究!哈哈哈。

圣小开:圣?大人?稣也姓圣!

捕快甲:笑话,圣大人全家我都认识,就没你这号。闭嘴吧你!

圣小开:啊……稣要投诉你们!

牢房

狱卒:姓名?籍贯?

圣小开:圣小开,金门人。

狱卒:身上有多少钱?

圣小开:没有你们这里的钱,人民币要么?只有四块钱。

狱卒:这是冥币?没钱就住最次的。

圣小开:请问大佬,稣是替死,还是充军?

狱卒:看情况,看表现,但由不得你。带进去!

捕快乙:这人脸瘫,看起来不怕死,试试培养成死士或细作?

狱卒:这个姓……应是假名,兴许就是敌国的细作。

捕快乙:先上点刑?

捕快丙:狱吏大人有令,将穿越者带去见他。

狱卒:穿越者?系奇装异服那厮?

捕快丙:与狱吏大人同姓者。带出来!

捕快甲:就是这位,整桩好好。

圣小开:敢问狱吏大人名号为何?

捕快丙:圣仙山大人。

圣小开:稣的先祖??

餐厅

圣仙山:开!终于见面了。甦准备了你最喜欢的牛排,六成熟,不加酱,快来吃吧。

圣小开:稣都没吃过牛排,你怎么知道稣喜欢吃?

圣仙山:这牛是甦培育的,保证你吃了就喜欢。

圣小开:太奢侈了,虽然稣经常和同学吹牛说牛排是高贵的食物,但稣还没钱吃。没想到居然在古代吃到。

圣仙山:甦有钱,随便吃。你以后也会有钱,能天天吃。

圣小开:嗯!真好吃。

圣仙山:试试这些,凤尾螺、青龙、鲟鳇,都是清蒸的,还有鱼羊汤。

圣小开:狱吏居然这么有钱?当官真好!

圣仙山:别的狱吏可没甦这么有钱。

圣小开:也对,你是神。啊!可太好吃了。稣真的还想再吃五百年。

圣仙山:那你为何跳楼?

圣小开:单纯好奇稣死后会是什么样的。

圣仙山:果然是甦的传人,可真舍得。

圣小开:如假包换。

圣仙山:但甦有个坏消息要告诉你,你女朋友也跳楼殉情了。

圣小开:稣有女朋友??

圣仙山:你学妹。

圣小开:em……稣是有个初中部的学妹,关系还行,但没确定过关系,学校不让早恋!

学校操场,跑步

胆公影究:开啊,你快全班第二了。不过和我这个第一,差距还很大。

圣小开:真喘!

胆公影究:嘘!刚刚那个学妹回头看了你一眼。

美梦里有怎样气候
你终于回过头看我

圣小开:快趴了……先扶着稣,看啥学妹。

胆公影究:脸上有一股英气,还是高个子,你喜欢的类型。

圣小开:这个背影是不错呀,快和稣一样高了!

胆公影究:继续跑,追她。

圣小开:你追吧,你比稣高,和她更搭。

胆公影究:果然是兄弟!

圣小开:笑死,稣在后面看你被打脸。

胆公影究:干!

学校操场,早操

有一天做早操,不同年级换位置。那天只看到背影的学妹居然排在稣的右边那排的第一个,这时候看到她英气逼人的脸了,结合身高和胆公影究的描述,稣可以确定就是她!但是稣不确定她记不记得稣,所以只是每天看着她的侧脸和背影。

慢慢地也知道,她是体育委员,性格泼辣,脾气火爆,经常对她班上的男生拳打脚踢,不是“打是情骂是爱”的那种,是当沙包踹的那种。

而且,后来知道她叫柯金钏,才明白那天她为何回头看稣!她的名字“金钏”的普通话和“真喘”的闽南语的发音特别像。嗯,是稣自作多情了,还以为她回头看稣是觉得稣闪耀着神性的光辉!

想明白的稣视线向着她,忍不住傻笑起来。没想到,她又回头瞟一眼稣,友好地问候了一句:“神经病?”

稣吓得只能承认,小声回答:“是啊。真喘!呵呵呵……”

过了几天,傍晚运动时间,稣特意跑操场满地找她,终于找到。她坐在沙堆边休息,稣过去傻笑她,“你也在喘啊?”

她一脸正经地说:“有点累,好像气血不足。”

稣顿时笑不出来,关心地问:“是不是低血糖?”

她说:“好像生病了!”

看着脸确实比平时白,穷稣只能安慰她:“长这么高,血是不容易送到脸上去。”

她想笑,但只是尴尬了一下。看来是真生病了。

稣只好约她一起去校门口喝葡萄糖。em,那时候穷,好多店真的就是把葡萄糖注射液当饮料卖。心想如果情况有好转,再一起吃个第一汤包也不错。

由于高中生不能和初中生早恋,所以此处情节省略一万行。

总之,在不影响学习的前提下,稣还翘课和她去学校后面爬大轮山,拜梵天寺。她是真去拜拜,稣是去看一页书在不在。

世事如棋,乾坤莫测,笑尽英雄啊!

逛三秀街,喝同样的果汁,吃同样的拉面。她冷了,还会直接叫稣脱衣服给她穿。有些瞬间,稣会心疼,原来她身体没看上去的好呀!

就算是轻轻的微风
也在试探思念浓薄
你忘的伞还依我的窗
望着窗外那悠悠春光
我心中延续和你的情感
有一种暧昧的美满
忘记了思念的负担

稣偶尔会和她说哪个学姐长得真漂亮,她也只是说自己还小,审美观不太一样,并没有其它情绪。所以,稣认为自己和学妹完全没有早恋!一点都不暧昧。

我自私延续心中的期盼
有一种暧昧的晴朗
站在这城市某一端
寂寞和爱
像浮云 聚又散

她说自己脾气很差,但是稣一点都不信——女人都是骗子。

spdlog

1. 用于啥需求?

打日志。spdlog 是一个高性能、易用的 C++ 日志库。

2. 何时使用 spdlog?

其格式化风格同 std::format/std::print,如果这符合您的习惯可以考虑。稣一般会在复杂场景下使用 Boost.Log,测试程序或简单的程序里使用 spdlog。

举例啥叫复杂场景:产品里有多个可执行程序(Executable),譬如说 A 和 B,它们共同使用多个动态链接库(Dynamic-Link Library),譬如说 X 和 Y。注意,这里说的“使用”,可能是静态加载,也可能是动态加载(比如动态链接库是插件)。当 X/Y 在 A 进程里时,它们的打印风格、设置都应该受 A 控制,而在 B 进程时,则受 B 控制。不管可执行程序和动态链接库有多少个,每个进程都应该只有一个 Logger。这个需求 Boost.Log 能轻松实现,而 spdlog 可能无法实现或实现并不轻松。2024 年在雪蛤油时,有个熟悉 spdlog 的同学和稣打赌,结果他用 spdlog 没能实现。也就是说即使 spdlog 能实现,那也不轻松。

3. 具体应用

首先注意到 Logger 的打印接口有两大类,一类是函数,比如 spdlog::info,只要你用了,它就被编译到程序里;另一类是宏,比如说 SPDLOG_INFO,它是否会被编译到程序里,受 SPDLOG_ACTIVE_LEVEL 控制。具体看以下注释:

1
2
3
4
5
6
7
8
9
10
11
12
//
// enable/disable log calls at compile time according to global level.
//
// define SPDLOG_ACTIVE_LEVEL to one of those (before including spdlog.h):
// SPDLOG_LEVEL_TRACE,
// SPDLOG_LEVEL_DEBUG,
// SPDLOG_LEVEL_INFO,
// SPDLOG_LEVEL_WARN,
// SPDLOG_LEVEL_ERROR,
// SPDLOG_LEVEL_CRITICAL,
// SPDLOG_LEVEL_OFF
//

spdlog 支持 6 种日志级别:

级别 说明 适用场景
trace 最详细的调试信息 开发调试
debug 调试信息 开发环境
info 一般信息 运行状态
warn 警告 潜在问题
error 错误(但程序可继续运行) 异常情况
critical 严重错误(可能崩溃) 致命问题

其中的 debug 级别,稣总觉得不应该存在,根据情况归到 trace 或 info 即可。

spdlog::set_level 设置的是运行时的显示级别,比如说:

1
2
3
spdlog::set_level(spdlog::level::debug); // 只显示 >= debug 的日志
spdlog::trace("This won't show (level too low)"); // 不会输出
spdlog::debug("Debug info"); // 会输出

但通常我们会使用宏来打印日志,并通过设定 SPDLOG_ACTIVE_LEVEL 来去掉低级别日志,以提高运行效率,或防止被“轻松逆向”。一般来说,Debug 版本可以设定 #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE,而 Release 版本可以 #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_WARN

既然我们使用宏来打印日志,那么我们就能注意到:宏也有两类,一类形如 SPDLOG_LOGGER_INFO,需要一个 logger 参数,另一类形如 SPDLOG_INFO,不需要传入 logger 参数。从以下代码可知,后者是默认 Logger 的打印宏。

1
2
3
4
5
6
7
8
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO
#define SPDLOG_LOGGER_INFO(logger, ...) \
SPDLOG_LOGGER_CALL(logger, spdlog::level::info, __VA_ARGS__)
#define SPDLOG_INFO(...) SPDLOG_LOGGER_INFO(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_INFO(logger, ...) (void)0
#define SPDLOG_INFO(...) (void)0
#endif

如果我们不想使用默认 Logger,就得自己创建 Logger,并使用第一类形如 SPDLOG_LOGGER_INFO 的宏,但这个宏有点长,还是把自己创建的 Logger 设置为默认,再使用第二类形如 SPDLOG_INFO 的宏方便点。

1
2
3
4
5
6
7
8
9
inline bool InitializeLogger() {
auto logger = std::make_shared<spdlog::logger>(
"", std::make_shared<spdlog::sinks::wincolor_stderr_sink_mt>());
if (!logger) {
return false;
}
spdlog::set_default_logger(std::move(logger));
return true;
}

至于输出格式,稣的测试程序一般如此设定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(int argc, char* argv[]) {
nw::args _(argc, argv);
nw::nowide_filesystem();
::SetConsoleOutputCP(CP_UTF8);

if (!InitializeLogger()) {
nw::cerr << "Failed to initialize logger!" << std::endl;
return EXIT_FAILURE;
}
#if _DEBUG
spdlog::set_level(spdlog::level::trace);
spdlog::set_pattern("%Y-%m-%dT%H:%M:%S.%e |%P~%5t <%^%L%$> %s#%#: %v");
#else
spdlog::set_level(spdlog::level::info);
spdlog::set_pattern("%Y-%m-%dT%H:%M:%S.%e |%P~%5t <%^%L%$> %v");
#endif
SPDLOG_TRACE("Starting up...");
// Other codes...
}

注意:默认情况下,spdlog 是同步的!如果日志量很大,应该使用异步日志(减少主线程阻塞)。例如:

1
2
3
4
5
6
7
8
9
10
11
inline bool InitializeLogger(std::string_view log_directory) {
spdlog::set_automatic_registration(false);
auto logger =
spdlog::create_async<spdlog::sinks::wincolor_stderr_sink_mt>("");
if (!logger) {
return false;
}
spdlog::register_or_replace(logger);
spdlog::set_default_logger(std::move(logger));
return true;
}

除了 spdlog::create_async,还有个 spdlog::create_async_nb,它们的差别在于堆积的日志溢出时的处理策略不同,具体查看:

1
2
3
4
5
6
7
// Async overflow policy - block by default.
enum class async_overflow_policy {
block, // Block until message can be enqueued
overrun_oldest, // Discard oldest message in the queue if full when trying to
// add new item.
discard_new // Discard new message if the queue is full when trying to add new item.
};

其它的……问掐鸡(LLM)吧!

通过 iPXE 安装 Debian

起源

想安装 Debian 12,但没 U 盘。

思考、观测

  • Debian 能通过网络安装,参考:Installing Debian using network booting

  • 但 PXE 太麻烦了,放弃。

  • 观测目标机器,发现其 EFI 有网络启动功能,还能按 Ctrl+B 进入 iPXE。

解决

  1. 开启网络启动,并关闭 Secure Boot。

  2. 看到 PXE 启动画面时,按 Ctrl+B,进入 iPXE。

  3. 输入 dhcp,使目标机器得到 IP 地址,成功即可下一步;但如果局域网内没有 DHCP Server,则需要手动配置:

1
2
3
4
5
6
7
set net0/ip 192.168.1.77
set net0/netmask 255.255.255.0
set net0/gateway 192.168.1.254
set dns 8.8.8.8

ifopen net0
ifstat net0

以上最后一条命令 ifstat net0 输入完后,应该能看到输出里有 open 的字样。

  1. 启动内核

以 Debian 12 为例,先设置网址前缀:

1
set URL http://mirrors.ustc.edu.cn/debian/dists/bookworm/main/installer-amd64/current/images/netboot/debian-installer/amd64/

注意,iPXE 里只支持 http。网址里的 mirrors.ustc.edu.cn 是中科大的镜像域名,在厦门、上海、长沙访问都很快。

1
2
3
kernel ${URL}linux console=ttyS1,115200n8 initrd=initrd.gz
initrd ${URL}initrd.gz
boot
  1. 加速安装

请参考《快速安装 Debian》,其中“按 Ctrl+Alt+F5 回到安装界面”这步需要改为“按 Ctrl+Alt+F1 回到安装界面”,因为通过 iPXE 启动的 Debian 安装程序是 TUI,运行于第一个控制台。

如何让开始按钮的右键菜单带加速键?

Win 键被禁用后发现 Windows 11 24H2 的一个小伎俩

起源

最近在开发基于 QEMU 的 Windows 虚拟机的 GPU 加速驱动。由于不想频繁按 Ctrl+Alt+G 退出虚拟机窗口的捕获状态,就不用鼠标设备,而是使用触控屏设备。即:
-device qemu-xhci -device usb-tablet
但这么一来,虚拟机就接收不到 Win 键了……于是之前迅速打开设备管理器的“Win+X, M”也跟着无效。

解决

“Win+X”的替代自然就是“鼠标右击开始按钮”,然而出来的菜单居然没有加速键?

No accelerator keys

用“Win+X”呼出的菜单应该是下面这样的:

With accelerator keys

于是猜测,微软一定会设计另一种机制,比如“按下某种组合键”,来提供有加速键的 Win+X 菜单。测试按 Ctrl 和 Shift,发现有时候第一次按无效,后面才会有效,索性再测试 Ctrl 和 Shift 同时按,则每次都有效。

所以,解决方式是:同时按下 Ctrl 和 Shift,再鼠标右击开始按钮。

RegameDesk 开发笔记【9】hello_imgui

本文的微信公众号链接

需求

客户端(主控)渲染窗口需要故障排除功能,比如说显示 FPS、丢帧、延迟等信息,以方便调试、测试。一个较为常见且靠谱的作法是基于 Dear ImGui 来实现。

学习

高手直接看 Dear ImGui 也不是不行。但如果是初学者,从 Hello ImGui 开始,比较不容易吓退。

Hello ImGui 基于 Dear ImGui,所以拿前者练好,再上生产环境用后者,是一个不错的路线。

实践

故障排除功能通常是一个彩蛋(隐藏功能),需要某种触发机制唤出。常见采用快捷键,但这可能和远程端系统里某个快捷键冲突。所以也可以考虑使用鼠标操作唤出,比如在顶部操作栏(显示状态下)连击 3 次右键。

Remote Desktop

RegameDesk 开发笔记【4】C++

C++ 20

根据 jetbrains 的统计,2023 年时,C++ 20 的使用率是 29%,仅次于 C++ 17 的 43%。在嵌入式领域和游戏开发领域,C++ 20 的使用率更高,分别为 37% 和 39%。

众所周知,C++ 版本越高,就越强大,并且解决以前版本的一些问题。讲道理的话,现在 2025 年就应该用 C++ 20。不用的人,大致理由都是成本问题。其实,大可以把工程的版本设置为 C++ 20,然后按照已经学会的版本去用。这种情况下,遇到问题,通常就是遇到旧版本的不足或过时的部分。按照稣的经验,目前主流大语言模型对 C++ 20 的支持是不错的,尤其是语法和标准库使用上。

参考:

CppCoreGuidelines

这是由 C++ 之父 Bjarne Stroustrup 领导的行业巨佬们写的宝典,对于写好工程有巨大帮助。有些初学者,买了工具书学完语法,就开始写代码,然后就会遇到一个典型的问题:代码写多了就乱,乱到一定程度自己都不想继续。即使是更好点的情况,靠毅力把代码写到上线,后面却发现乱得自己都不想维护。CppCoreGuidelines 就是用来解决这类问题的。

入门 CppCoreGuidelines 的第一步是:把 GSL 用起来。

参考:

第二步是:记住不要写 STUPID 的代码。仔细看看 CppCoreGuidelines,里面有具体规则。

  • Singleton - 单例(I.3: 避免使用单例)

  • Tight Coupling - 紧密耦合(C: 类和类层次)

  • Untestability - 不可测试(P.12: 适当采用支持工具、P.13: 适当采用支持程序库)

  • Premature Optimization - 过早优化(Per.1: 请勿进行无理由的优化、Per.2: 请勿进行不成熟的优化、Per.3: 请勿对非性能关键的代码进行优化)

  • Indescriptive Naming - 非描述性命名(P.3: 表达你的设计意图)

  • Duplication - 重复代码(ES.3: 避免重复(DRY),避免冗余代码)

RegameDesk 开发笔记【3】Boost 1.88.0

前言

今天是 2025-02-20,Boost 1.88.0 还没发布,但目前的版本在实现 RegameDesk 时遇到一些问题,导致使用了不优雅的解决方案,按照稣和作者们的沟通,下个版本都能解决,所以稣认为 1.88.0 才是适合远程桌面​的 Boost 版本。

Why Boost?

在开发过程中,选择一种基础库或库的集合(注:实际上 Boost 是一个集合)几乎是必然的。如果您的产品使用了 Qt,那么许多基础功能很可能会直接借助 Qt 来实现。有些项目可能会选择 Google 的代码作为基础,从而引入 Abseil。甚至还有一些团队会单独提取 Chromium 的 base 模块来使用。

稣曾遇到过有人推荐使用 Folly,而 Folly 本身也依赖于 Boost。如果您能够接受 Folly,那么当团队不再使用它时,接受 Boost 也应该不是问题。

以上例子其实暗示了一个事实:C++ 标准库的功能相对有限,难以满足实际项目的需求。为了解决这一问题,开发团队通常会选择引入第三方库。在选择第三方库时,主要有两种思路:一种是引入一个功能强大且尽可能全面的大型库(集合),再配合少数其它必要的小型库,以最大化地保持代码风格的一致性;另一种则是引入多个专门解决特定需求的小型库,这种方式虽然可以让每个库都“小而美”,但可能会导致代码风格的不一致。选择哪种方式其实很简单——选择您最熟悉的那一种。

注意!上一段说的“小而美”,从整体上看,可能是假象,尤其当项目很大、成员较多时。当然您可以提出 Chromium 来反驳,不过您需要一定实力和精力去驾驭,所以稣只是说“可能”!

对稣来说,Boost 是一个显而易见的选择。它经过多年的发展,积累了丰富的文档和社区讨论,学习和使用难度都不大。过去有人抱怨 Boost 的编译时间过长,现在早已不存在。而且在如今普遍配备 64GB 内存、高速 SSD 的开发环境中,加载大量头文件已经不再是问题,怪罪 Boost 使工程加载变慢的人也能放心了。

片面地安利 Boost

使用 Boost 相比自己实现而言,有以下好处​:

  1. ​开发更快;

  2. 运行更快;

  3. 运行更稳​;

  4. 代码可读性更高。

后面会举真实例子说明为啥是和自己实现比!先说事实,以上三条总有 1~2 条符合,甚至对于某些团队——可能是中 3~4 条。

首先,开发更快,可能是最有争议的,很多人会反驳说——光学它就要很多时间​。这要是放在以前,稣可能想不出啥好招给这部分人洗脑,现在有大语言模型,各种辅助手段,如果还这么说,完全也不用去反驳,没必要了。

另外,Boost 有部分库确实性能不行,并且官方文档也是明说的,这种情况是求稳定,比如 Boost.Format​。

剩下的,Boost 通常有十分优秀的性能,除非极端的具体领域优化,不然大多数人能把性能写赢 Boost 的概率几乎是 1%(多给 1 分,怕您是真大佬!)。举个例子,Boost.JSON 的性能是高于 RapidJSON 的。RapidJSON 这名字起得好(快),不一定就是真的好(快)。

等等,第 4 条是怎么回事?有些人会说他自己手撸的更好理解,如何反驳?嗯,很可能只是对于作者本人才更好理解,别人看都不想看(笑)。作为团队合作的产物,更多共同点才是好的。比如说,整个团队都熟悉 STL,那么基于 STL 的接口/实现就不会差,而把它等价地改为基于某个第三方库,如果没有强力的理由,通常会被(不用脑地)认为不好。自己写的,对别人来说,何尝不是一种“第三方”?显然,大家普遍认可的“第三方”才可能是更好沟通的,更好达成共识的。(注:这里的“大家”是普遍意义上的大家,不是说某个团队里的少数几名成员,毕竟有的团队就两名写代码的,并不存在“多数”和“少数”!)

例子

大家最喜欢的案例分析来了……稣正好遇到这样一个活生生的例子:实现一个 IPC​ 用于 Service 和工作进程之间通信。

这个故事发生在雪蛤油打工时。一开始,稣就打算使用 Boost 封装的 Pipe,因为以前干过类似的活,​有成功案例。但不幸的是,稣是第二个加入团队的,原来已经有人弄过一个实现,纯手撸的,基于共享内存和事件通知​。关键是,大佬说这实现在他前公司用了 2 年很稳定​。稣想了一下,虽然自己的实现代码量不到它的 1/5,但只接受了几周的考验​。还是别冒险,于是那份手撸版本上​了生产。

然后有一个周末,没回家,随手拿 example 改改对比性能,使用 Boost 的版本速度居然是那手撸实现的 4 倍!在应用层,共享内存是 Windows 上最快的 IPC 机制没错,但它需要其它内核对象的辅助,最终完成时速度就拖慢了。Boost 使用 Pipe 作为 IPC 机制,而没用共享内存和事件通知复合实现,很可能作者是知道这门道的​。

对了,每次发送多少字节对性能是有影响的,如果您打算测试,需要对不同大小的信息进行测试,不能用固定的大小,以免得出不全面的​结论。

参考

来自 CppCoreGuidelines 的一节:

  • ES.1: 优先采用标准库而不是其他的库或者“手工自制代码”

总结

本文,乃至本系列文章,重点在于心法,并没打算详细介绍项目里用了 Boost 具体哪些类库。因为很简单的道理:您要的功能,如果 Boost 有,考虑用它即可。

看到这里,如果您还记得稣写的基于 Boost 的 IPC 代码,它又多接受了三个月的考验——极其稳定,重点是它用起来简单多了。