Cpp lambda 表达式笔记
在另一篇关于可调用对象的笔记中已经对lambda表达式的语法本质和应用情景进行了整理, 这篇笔记主要是整理lambda表达式的语法细节,假定读者对lambda表达式已经有了基本的概念。 虽然早在C++11中就提出了lambda表达式,但是相关的语法细节始终在不断地发展和完善(C++实在是太复杂了!), 本文以C++20已经支持的语法为主,对于最新的C++23增加的语法不作讨论,例如Deducing This等内容。 基础 基本捕获 首先介绍两种隐式捕获符: [=]:全部按值捕获 [&]:全部按引用捕获 它们会自动地捕获在lambda表达式中所有实际被使用的局部变量,无需我们逐个列出被使用变量对应的名称。 这里存在一个问题:如果当前处于一个普通成员函数中,如何处理特殊的this/*this所代表的当前对象?见下文中的讨论。 在默认捕获符的基础上,我们可以进行一些微调,例如: [=, &a, &b]:表示除了后面明确提到的这些变量按引用捕获,其它情况下默认按值捕获 [&, a, b]:表示除了后面明确提到的这些变量按值捕获,其它情况下默认按引...
Cpp 可调用对象笔记
概述 在C++中,可调对象(Callable Objects)是指可以像函数一样被调用的对象,通常包括: 函数指针 仿函数 std::function lambda 表达式 它们大部分都是基于面向对象实现的,但是函数指针是个例外,因此给对象两个字加上引号其实更合适。 可调用对象是函数的扩展概念,引入它们的主要目的就是补上函数天生的短板: 函数在语法上不可以像变量一样作为参数被传递给其它函数;可调用对象可以。 函数在语法上通常不具有内部状态,或者即使有,也只是通过局部静态变量实现的唯一内部状态;每一个可调用对象都可以拥有独立的内部状态。 这使得可调用对象在泛型编程、回调函数和函数式编程中发挥重要的作用。 函数的这两个短板是针对C/C++这种系统级编程语言来说的,但是对于某些高级语言来说,这些完全不是问题, 例如对于JavaScript、Python和Lua来说,函数在语法上就是一个可调用的变量,可以像普通变量一样直接作为参数传递给其他函数,并且允许拥有内部状态,称之为闭包可能更合适。 因为这些高级语言的执行由其解释器或虚拟机负责,而C/C++需要直接执行。 TODO:...
高精度时间戳的获取
获取毫秒级的高精度时间戳是一个很常见的需求,尤其在向日志文件中输出信息时通常需要附带格式化的时间戳, 下面在不同的语言中尝试生成形如 [2024-07-30 00:52:47.379] 的高精度时间戳。(虽然在大部分语境下,时间戳是一个非负整数,但是为了方便使用,这里统一为含毫秒的固定格式的时间字符串) C++ 对于C++,标准库 chrono 可以获取高精度的时间, 然后通过 localtime 函数进行格式化,由于它不支持毫秒部分的格式化,我们还需要对毫秒进行额外处理。 下面是一个生成时间戳的示例函数 12345678910111213141516static std::string time_stamp() { // unsafe auto now = std::chrono::system_clock::now(); auto now_time_t = std::chrono::system_clock::to_time_t(now); auto now_ms = std::chrono::duration_cast<std::chron...
XMake 速成笔记
虽然xmake的使用远没有cmake那么普及,cmake在可预见的将来都还是c++项目事实上的标准, 但是对于一些个人的小项目,直接使用xmake看起来还是更方便的,因此简单学习一下使用方式。 下载安装 虽然XMake支持了很多种安装方式,但是我还是选择了纯净版安装。 在Github仓库中选择xmake-[version]-win64.zip下载到本地解压,然后把对应路径添加到PATH环境变量即可。 Demo项目 xmake可以直接生成C++的HelloWorld项目 1xmake create -l c++ -P ./demo 这个命令会在当前位置下创建./demo文件夹,在其中自动生成如下文件结构 12345demo├── src│ └── main.cpp├── .gitignore└── xmake.lua 其中xmake的核心配置文件xmake.lua有效内容如下 12345add_rules("mode.debug", "mode.release")target("demo") set_kind(&...
Lua 速成笔记
简单学一下Lua这个有点过时的轻量级脚本语言吧,因为很多工具(nvim、xmake、MySQL等)都采用了Lua脚本提供配置, 而且直到现在,在c++项目使用Lua脚本提供配置也是一个可以考虑的方案。 关于Lua的教程很多都是速成版的,因为内容实在比较简单,比如Learn Lua in Y minutes。 编译安装 Lua是一种开源的脚本语言,完全使用C语言编写,Lua官网直接提供了源码, 在Linux系统中的下载和源码编译流程如下 1234curl -L -R -O https://www.lua.org/ftp/lua-5.4.7.tar.gztar zxf lua-5.4.7.tar.gzcd lua-5.4.7make all test 编译命令非常简单,编译完成后可以得到两个产物:lua和luac,两者仍然存放在src/文件夹中,将其移动到其它位置, 然后将路径添加到环境变量即可。其实编译产物中还有一个Lua库,用于嵌入到C语言项目中,但是暂时不需要。 由于Lua的源码编译过程本身非常简单,我们可以迁移到Windows上进行源码编译,为了方便还可以把Makefile改...
Cpp 智能指针
概述 C++标准库主要提供了三种智能指针:(都在<memory头文件中) shared_ptr:共享指针,允许资源共享,在内部维持一个引用计数,复制会增加引用计数,析构则会减少引用计数,引用计数为零则释放资源 unique_ptr:独享指针,独自负责资源的管理,析构时释放资源 weak_ptr:弱共享指针,作为共享指针的辅助手段,可以指向共享指针的内容但是不参与引用计数,主要为了解决共享指针的循环引用问题 除此之外,在早期还提供了auto_ptr,但是目前已经被废弃。下面先介绍RAII思想,然后介绍三种智能指针的使用。 智能指针的正确使用可以在很大程度上提供内存安全的保障,但是这其实是不够的,C++ 说到底是一个无法保证内存安全的语言。 简单示例与RAII 先给出使用原始指针和unique_ptr管理资源的对比示例 123456789101112131415{ int* p = new int(100); // ... delete p; // 必须记得手动释放内存}{ std::unique_ptr<i...
Cpp 多线程学习笔记——3. future 异步编程
概述 C++通过<future>头文件提供了一组支持异步编程的工具,使用这些工具比直接进行多线程操作更加高级、更加简便。 主要包括如下的类型: std::future:表示异步操作的结果,这个结果在未来可能可用,支持查询操作的状态,等待操作完成和获取结果。注意用于获取结果的get()方法调用会阻塞当前执行流,直到结果准备就绪。 std::promise:承诺在未来提供一个可用的值,通常与 std::future 配对使用,set_result()可以设置异步操作的结果。可用get_future()提取获得一个关联的std::future对象。 std::packaged_task:封装一个函数或可调用对象,使其可以作为异步任务执行。可用get_future()获得一个关联的std::future对象。 还包括如下的函数: std::async:用于启动异步任务,返回一个std::future对象代表任务的结果,注意我们必须要用变量接收这个返回值,否则当前语句会阻塞式的等待任务结束,因为只有异步任务结束才会销毁返回的临时变量! 这里std::future和st...
Cpp 多线程学习笔记——2. 互斥锁、原子操作与条件变量
互斥锁 线程安全与互斥锁 在多线程编程中,由于多个线程存在共享的资源(例如全局变量等),因此可能导致相互之间产生干扰, 下面的例子可以展示这种问题(必须使用Debug模式编译,因为Release模式下可能直接优化了) 12345678910111213141516171819202122232425262728293031323334353637#include <iostream>#include <thread>#include <vector>const int num_threads = 5;const int num_increments = 10000;const int num_experiments = 20;void increment_counter(int &counter) { for (int i = 0; i < num_increments; ++i) { counter++; // 多线程同时修改共享变量 }}void run_ex...
Cpp 多线程学习笔记——1. 线程
多线程的实现 C/C++ 多线程 我们关注 C/C++ 的多线程语法,按照平台和封装层次的不同,有几种常见的实现: 对于POSIX系统(Linux系统等),可以使用pthread库实现线程操作 对于Windows系统,同样提供了线程操作的API 对于Modern C++,可以使用std::thread进行跨平台统一的线程操作,相当于对不同操作系统的多线程操作的封装 值得注意的是,C++11引入了std::thread,但是这个线程类的设计有些缺陷(不是RAII的),后续填坑时为了不破坏兼容性,C++20又设计了一个新的名为std::jthread的线程类,仍然存放在<thread>头文件里面。 由于三家编译器的较新版本默认采用C++17标准,使用std::jthread时需要在编译选项中指明采用C++20标准,例如-std=c++20或/std:c++20 下面使用C/C++最常见的三种实现,编写等价的多线程程序示例,可以发现在不同的实现中的操作都是类似的,前两者都是C语言风格的函数接口,区别仅仅是参数的格式和类型等细节,而std::thread则使用了类进行封装...
Cpp 设计模式笔记——5. 更多模式
随着软件工程的不断发展,除了经典的二十几种设计模式,还有更多设计模式在不断涌现,其中一些设计模式也是值得学习的。 对象池模式 对象池(Object Pool)是一种创建和管理对象的设计模式,特别适用于需要频繁创建和销毁对象的场景。 它通过复用对象来减少对象的创建和销毁次数,从而提高性能和资源利用率,在资源密集型的应用情景中(如数据库连接、线程、Socket连接、内存分配等),池化的思想被广泛使用。 对象池的实现示例是非常简单直观的,直接看代码即可。 与之不同的是,使用C++实现一个实用的线程池或内存池的代码细节会更加复杂,本文中不作讨论。 示例代码如下 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#include <iostream>#include <memory>#include <vector>// 对象类class PooledObject {public: PooledO...
