Web Analytics
yangyang

码农兼一个普普通通小青年

All Posts in 2025


C++语言演化中的审慎原则

本文深入剖析了C++语言演化中所秉持的“审慎原则”,论证了标准委员会(WG21)通过有意识地拒绝或搁置某些语言特性,从而成功维护了C++的长期稳定性与核心设计哲学。报告通过四个关键案例展开论证:一、在处理多重继承的虚函数名冲突时,委员会选择推广设计模式(适配器模式)而非引入新“重命名”语法,以避免语言膨胀。二、面对双重分发的需求,它采纳了访问者模式,拒绝了可能导致对象模型过度复杂化的原生语言支持。三、它坚定地拒绝内置垃圾回收(GC)机制,以捍G卫RAII范式、零开销原则及性能的确定性,这些是C++的立身之本。四、在类型推导上,它没有直接采纳当时已有但存在瑕疵的typeof,而是精心设计了语义更精确的decltype,彰显了对语言基础工具正确性的极致追求。综上所述,这些决策共同揭示了C++标准化过程中的一种成熟智慧:通过审慎的“减法”来保证语言的健壮、高效与持久生命力 …

Visitor Pattern Double Dispatch RAII C++ Evolution Language Design Philosophy Prudence Principle Multiple Inheritance

CMake入门教程

CMake已经成为C++生态系统中事实上的标准构建系统生成器。它通过将复杂的构建逻辑抽象到一个统一的、跨平台的脚本语言中,极大地提高了项目的可维护性和可移植性。掌握现代CMake,特别是其基于目标的依赖管理思想,是每一位严肃的C++开发者必备的技能。本教程从CMake的基础工作流程和核心语法出发,深入探讨了作用域、库的构建与链接、条件编译以及高级模块化技术。我们强调了“源码外构建”的重要性,详细解析了PUBLIC、PRIVATE和INTERFACE关键字在传递依赖关系中的核心作用,并针对Linux平台特别讲解了RPATH和$ORIGIN在创建可重定位软件包中的应用。通过对比function与macro、target_sources与file(GLOB),我们阐明了现代CMake的最佳实践及其背后的设计哲学。最终,通过一个完整的实战项目,我们将所有理论知识融会贯通,展示了如何构建一个结构清晰 …

cmake Cross-Platform Out-of-Source Build RPATH Position-Independent Code Conditional Compilation target-link-libraries

Windows服务“伪运行”之谜:从一个时间同步服务的失灵谈起

本文通过一个时间同步服务的具体案例,深入探讨了Windows服务在“快速启动”模式下出现的“伪运行”问题。该问题的典型症状是:服务设置为自动启动,在“关机”再开机后,其状态显示为“正在运行”,却没有执行其OnStart方法中的关键启动任务(如首次时间同步)。文章剖析了此现象的根本原因在于Windows的快速启动机制。该机制通过休眠并恢复系统内核及服务进程来加速开机,这个过程会完全跳过OnStart方法,导致服务从一个过时的、无效的内存状态中“假醒”,成为“僵尸服务”。为解决此问题,文章提供了一套完整的排查与解决方案。首先,介绍了如何通过正确配置Windows事件日志来进行有效诊断,并指出了创建事件源的权限要点。其次,说明了如何通过禁用快速启动来快速验证问题根源。最终,文章给出了最健壮的根本性解决方案:在C#服务代码中重写OnPowerEvent方法,使其能够响应系统的Suspend …

Windows Service Fast Startup OnStart OnPowerEvent ResumeSuspend Hybrid Shutdown Event Log Service Troubleshooting

不止于值:解析.NET中“赋值即触发”的状态驱动事件模式

即便是给一个对象的属性赋上与它当前完全相同的值,相关的“已更改”(XXXChanged)事件依然被触发了。一个典型的例子就是在使用 System.Data.DataTable 或System.Configuration.ApplicationSettingsBase时。研究源码发现,ApplicationSettingsBase 和 DataTable 的“赋值即触发”行为,并非缺陷,而是.NET框架中一个优雅且实用的状态驱动事件模式的体现。它将数据容器从简单的值存储器提升为强大的状态机,极大地简化了数据持久化和同步的复杂性。它选择牺牲微不足道的判断性能,来换取核心功能(状态跟踪)的绝对可靠性。理解了其“关注状态,而非价值”的核心思想,并洞悉其源码实现后,我们就能根据实际场景,明智地选择是利用还是“修正”这一行为,从而编写出更健壮、更高效的.NET应用程序。 …

ApplicationSettingsBase PropertyChanged

使用NTP协议实现时间同步客户端和授时服务端

在之前写了两篇关于NTP的文章,一篇是使用NTP协议实现本地时间跟NTP授时服务器进行同步介绍了NTP协议的原理,另一篇是实现一个时间同步客户端NTPClock介绍了使用WPF编写一个模仿NTPClock的时间同步的App,因为Windows的自动较时功能不是很完善,所以这个应用我自己使用了很长时间。 在这篇文章里,我准备将前面两篇文章总结一下,对一些方法进行重构,另外实现一个利用NTP来实现授时的服务端,以完善整个功能。一般的我们都是使用一些公开的NTP授时服务地址,比如"time.windows.com"、"pool.ntp.org"等来获取时间,这有一些限制: 必须要求这个服务器能够访问和解析这些授时服务器地址,这在有些机房的内网计算机上其实是无法满足条件的。 这些公开的NTP授时服务器大多有请求频率限制,如果请求次数多过或过快,可能会请求失败。 有时候,我们有自己的GPS接收装置 …

NTP

浅析C#中异步编程以及TAP模式

虽然C#转眼间已经发展到了10.0版本,但有些在 C#5.0 引入并随.NET Framework 4.5一同发布的特性比如async和await我仍不是很清楚,但这些新特性在一些人工智能生成的代码中使用的越来越多,因为它们确实能够提高异步编程时代码的清晰度,所以最近几天好好研究了一下它的原理。异步编程可以解决线程因为等待独占式任务而导致的阻塞,但异步编程一直是一个难点,如何正确实现异步模式却不容易,因为它涉及到诸如状态管理,异常管理,同步上下文,异步任务组合等等诸多问题。C#中有很多异步编程模型,最早的有APM (Asynchronous Programming Model) ,随后有EAP(Event-Based Asynchronous Pattern)以及建立在EAP基础上的BackgroundWorker等等,这些异步编程模型在场景比较简单时可能可以满足要求,但 …

Async Callback TAP APM EAP async await

一个完整的阻止远程桌面登录攻击的程序

在大概一年多以前,我写了一个简单的通过程序判断和阻止来自特定IP地址的远程桌面连接攻击的程序,一直以来是作为一个简单的控制台程序来使用的,用起来效果很不错。最近我在自己的阿里云服务器上也发现了很多这种远程桌面登录连接的尝试,但这也没有办法,只要端口对外网暴露,就免不了会有这种攻击。于是我对之前的那个小程序做了一些调整和完善,主要改进了: 程序不一定要监控特定端口,只要在日志里查找到了类型ID为4625的错误日志,就认为是远程桌面连接登录。 当远程桌面登录失败尝试超过3次后,除了添加到防火墙之外,还记录该远程信息到日志,包括远程IP尝试登录时采用的用户名,IP归属地等信息。 平台使用最新的.NET 8.0来重写,程序可以作为控制台程序,或Windows Service 安装运行。 添加白名单机制,对于白名单里面的IP地址不做登录失败校验。 …

Dependency Injection Background services RDP netsh advfirewall NetFwTypeLib

从零开始申请Google Gemini Advance 15个月免费试用

最近大模型热火朝天,于是我也开始在工作中尝试使用这类工具来提高编码效率。工作中编码时,我主要使用的是豆包,它有专门的桌面客户端。本文介绍了如何申请Google Gemini Advance的15个月免费试用,内容包括如何使用稳定线路的美国网络,如何提供满足条件的Google账户,如果申请美国edu邮箱,如何申请符合条件的美国信用卡,在满足以上条件之后,如何免费申请Google Gemini Advance的15个月学生优惠免费试用。 …

gemini advance student discount

自定义JSON序列化

JSON的序列化和反序列化在工作中偶尔会用到,特别是在处理网络请求的时候。通常的网站或者api接口会提供json数据格式,要将json字符串转换为C#中的对象,就要用到序列化和反序列化。恰好最近刚有一个需求,需要从某个网站上获取一下Shibor数据,这里涉及到了自定义的JSON序列化,简单记录一下。 …

Shibor JSON Serialization

解决Socket编程中对端意外断开而导致的“卡死”问题

在上一篇文章中提到了最近写的一个网络收发程序的一个问题。一开始还以为是因为计算机休眠会把线程杀死,导致程序在唤醒后接收不到服务端程序发送的数据,后来通过使用WinDbg分析dmp文件发现在网络异常断开后,接收数据的线程并没有退出,而是程序卡在了NetworkStream.Read方法那里,即使后来网络连通了也得不到通知和接收不了数据。这就引出了本篇要解决的问题,即在网络收发中,如何解决由于网络意外断开,客户端程序Socket的卡在了网络读取方法的问题。解决方法有两种:一种是在客户端读取数据时添加超时,另外就是增加心跳。 …

socket heartbeat

使用WinDbg查看所有的C#线程及其名称

最近程序遇到了一个问题,当写的一个C#客户端程序在Windows 11系统休眠之后再次唤醒,有一定的概率不能够正常的接受服务端的数据。这个程序有一个专门用于连接服务端的C# 线程,名字为"WatchServiceTransport",它的内部是一个while循环,不停的从服务端使用Socket的NetworkStream.Read读取数据。在正常的时候,程序是能够接收数据的。但是当Windows休眠之后,此时网络连接可能断开,此时即使Windows唤醒之后仍然不能连接收取数据。于是怀疑是否是因为Windows 11休眠后,连接TCP的线程被强制退出了。那如何验证是否是这个原因导致的呢?就需要判断在出现问题时,当前的名为“WatchServiceTransport”的线程还是否存在。 幸好,今天这个问题又复现了于是导出了DUMP文件,用Windbg分析了一下,顺便记录如何打印所有的C#线程 …

WinDbg dumpheap

C++中的shared_ptr、weak_ptr以及enable_shared_from_this

在《Linux多线程服务器编程:使用muduo C++网络库》这本书中,作者在第15页一针见血的指出C++可能出现的内存问题的几个方面: 缓冲区溢出(buffer overrun) 空悬指针(dangling pointer 指向已销毁的对象或已回收的地址)/野指针(wild pointer 指的是未经初始化的指针) 重复释放(double delete) 内存泄漏(memory leak) 不配对的new[]/delete 内存碎片(memory fragmentation) 正确的使用智能指针可以解决前5个问题: 缓冲区溢出:使用STL中的容器( std::vector 、 std::string )、使用安全的拷贝函数,或者自己编写buffer class来管理缓冲区,自动记住缓冲区的长度,并通过成员函数,而不是裸指针来修改缓冲区 空悬指针/野指针:用 std::share_ …

weak callback weak ptr shared ptr

解决OneNote闪退的问题

本人笔记一直使用的是OneNote,因为它既有Windows等桌面程序版本,也有iOS等移动端版本,使用起来很方便。 但今天OneNote打开时突然出现问题,表现就是点开OneNote之后,界面卡住,等待几秒之后,程序直接崩溃。在任务管理器里,有时候能进程还在,有时候没有。在网上找了一下解决方法,以安全模式启动OneNote,删除了缓存,成功解决,这里记录一下。 按下 “Win + R” 组合键,打开 “运行” 对话框,输入 “onenote /safeboot” 并回车:会弹出以下界面: …

onenote safeboot

C++编译器中最烦人的分析机制

Scott Meyers 在《Effective STL》一书的第六条提出了一个“当心C++最令人烦恼的解析"(the most vexing parse)的问题。“the most vexing parse” 这个术语来形容 C++ 标准对于 declaration 语句的消歧义(ambiguity resolution)约定与常人的认知相悖。最令人烦恼的解析 (most vexing parse)是C++中的一种反直觉的二义性解析形式。 在一些场景下,编译器无法区分某语句是初始化时某对象的参数,还是声明一个函数时指定参数类型。在这些情况下,编译器将该行解释为函数声明。简言之就是“调用构造函数被误认为是函数声明的问题”,即 形如 Type() 或 Type(name) 的表达在某些情况下具有歧义(syntax ambiguity)。 …

vexing parse

C++中的bind、function以及lambda表达式

在C++98中就有了函数对象(function object),为了方便从二元函数得到一元函数,引入了bind1st和bind2nd,这种场景在C++ STL中应用的很常见。随后C++11从boost库中引入了功能更强大的bind (以替代bind1st和bind2nd) 以及function函数对象机制。由于函数对象仍然需要额外定义,且有时很不方便,所以又引入了lambda表达式。 本文逐一介绍这些概念,以及背后的实现原理。 …

Lambda Expression bind function

C++中的智能指针

C++中堆上的动态资源管理是一个容易出错的地方,借助RAII(Resource Acquisition Is Initialization,资源获取即初始化),能够简化资源管理,依此思想产生的智能指针极大的简化了编程。 本文先简要介绍C++中资源管理,然后介绍RAII,最后在这些概念上实现一个简单的不带引用计数的智能指针和带引用计数的智能指针,以加深对这些知识的理解。 C++中的栈、堆及资源管理 与C#中的概念相似,在C#中,值类型的对象大多数情况下是在栈上分配的,而引用类型的对象,则默认在堆上分配,这些都是默认行为,不需要手动指定,栈上的内存在出作用域后会被回收,而堆上的内存,则由垃圾回收器负责回收。 在C++中则复杂的多,它有三块内存区域: 静态内存,用来保存局部static对象、类的static数据成员,以及任何定义在函数之外的变量,分配在静态内存中的对象由编译器自动创建和销毁。 …

RAII reference count

C++中的Copy-And-Swap惯用法

在C++中,有个著名的The Big Three,即:如果要显示声明析构函数、拷贝构造函数和拷贝赋值操作符,那么需要显示声明所有的这三者。拷贝构造函数和析构函数实现起来比较容易,但是拷贝赋值操作符则要复杂很多。因为与拷贝构造不同,拷贝赋值需要先把当前的资源释放,然后重新构造。这与之前单例模式中的某些场景一样,有许多需要注意的问题。copy-and-swap就是完美的解决方案。它可以很好地帮助拷贝赋值操作符达到两个目标:避免代码重复、提供强烈的异常安全保证。 …

move semantics copy and swap move assignment cplusplus idiom

C++中的移动语义和完美转发

在C++中,对象默认都是值语义,比如在对象A里面定义了成员变量B和C,那么B和C会被直接放在A的内存空间里,这样做的优点是保证了内存访问的局部性,这在现代处理器架构上具有绝对的性能优势。但缺点是,在对象复制时,具有很大的开销。而在Java或者C#里面,默认存储的是引用或者说是指针。因为C++存储完整对象这个特性会导致大的复制开销,所以C++需要移动语义这一优化,而在Java或者C#中则根本不需要这一概念。一句话总结就是,移动语义使得C++里返回大对象(比如容器)的函数和运算符的性能得到极大提高。 …

perfect forwarding rvalue lvalue move semantics

重置Windows应用商店和安装Microsoft To Do

最近想在Window系统上安装一个包含计划任何和闹钟提醒的应用,之前这些东西一般是在手机上完成的。后来发现微软花大价钱购买了一款名为To Do的应用,并起名为了Microsoft To Do,这是一款跨平台的应用,所以比较符合我的要求。但当我访问它的网站时,点击下载发现没有任何反应。后来我才意识到它是一个Windows Store应用程序,跟之前的WinDbg一个德性,看来时需要一定的技巧才能安装的上。 …

reset-windows-store microsoft-to-do