Cpp 智能指针
概述 C++标准库主要提供了三种智能指针:(都在<memory头文件中) shared_ptr:共享指针,允许资源共享,在内部维持一个引用计数,复制会增加引用计数,析构则会减少引用计数,引用计数为零则释放资源 unique_ptr:独享指针,独自负责资源的管理,析构时释放资源 weak_ptr:弱共享指针,作为共享指针的辅助手段,可以指向共享指针的内容但是不参与引用计数,主要为了解决共享指针的循环引用问题 除此之外,在早期还提供了auto_ptr,但是目前已经被废弃。下面先介绍RAII思想,然后介绍三种智能指针的使用。 简单示例与RAII 先给出使用原始指针和unique_ptr管理资源的对比示例 123456789101112131415{ int* p = new int(100); // ... delete p; // 必须记得手动释放内存}{ std::unique_ptr<int> up = std::make_unique<int>(200); //... // u...
Cpp 多线程学习笔记——5. 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 多线程学习笔记——4. 条件变量
介绍 在C++中通过<condition_variable>头文件提供了条件变量类型std::condition_variable和std::condition_variable_any, 条件变量提供了一种线程间的同步机制:允许一个或多个线程等待某个条件变为真,同时另一个线程可以改变这个条件并通知等待的线程。 条件变量通常需要和同步锁结合使用。 std::condition_variable_any与std::condition_variable非常类似, 区别仅仅是std::condition_variable的wait函数只能接受std::unique_lock<std::mutex>类型的参数, 而std::condition_variable_any可以接受任何lockable参数,相应地需要付出额外开销。 除此以外,两者的使用几乎完全一样。 下文中只讨论std::condition_variable的使用,一般也不推荐使用std::condition_variable_any。 使用方法 std::condition_variable提供了几种...
Cpp 多线程学习笔记——3. 原子操作
原子类型和原子操作 原子操作是指在多线程编程中对共享数据的操作是不可分割、不会被中断的操作, 这意味着操作不会被其他线程干扰,不会被调度切换,要么一次性执行完成,要么完全不执行,不存在第三种状态。 原子操作可以用于避免数据竞争和保证线程安全,不过显然我们需要为安全性付出额外的性能开销。 原子类型是一种特殊的数据类型,在底层保证对原子类型变量的相关操作是原子操作, 例如对原子类型变量的读取、写入、交换、递增、递减等。 这里我们再次重复前一篇使用的例子,只是改动了共享变量的定义:使用原子类型的整数变量shared_counter而非通常的整数变量 1234567891011121314151617181920212223242526272829303132333435363738#include <atomic>#include <iostream>#include <thread>#include <vector>const int num_threads = 5;const int num_increments = 10000;co...
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_experi...
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则使用了类进行封装,使用更加简洁。 使用pthread的示例如下...
Cpp 设计模式笔记——7. 相关概念
除了设计模式,还有几个编程概念与之相生相随,尤其在Java中被广泛的应用:面向切面编程、控制反转和依赖注入。 下面简单学习一下这几个概念,并在C++/Python中尝试应用。 面向切面编程 面向切面编程(Aspect-Oriented Programming, AOP)是一种编程范式,它旨在提高软件模块化,通过将横切关注点(Cross-cutting Concerns)与业务逻辑分离来简化程序结构。 横切关注点是指那些分散在多个模块中的功能,如日志记录、事务管理、权限控制等,这些功能虽然对多个模块都重要,但与核心业务逻辑无关。 在Java中,Spring框架广泛支持AOP,例如我们可以使用Spring AOP来添加日志记录的切面,主要代码如下 1234567@Aspectpublic class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { ...
Cpp 设计模式笔记——6. 更多模式
随着软件工程的不断发展,除了经典的二十几种设计模式,还有更多设计模式在不断涌现,其中一些设计模式也是值得学习的。 对象池模式 对象池(Object Pool)是一种创建和管理对象的设计模式,特别适用于需要频繁创建和销毁对象的场景。 它通过复用对象来减少对象的创建和销毁次数,从而提高性能和资源利用率,在资源密集型的应用情景中(如数据库连接、线程、Socket连接、内存分配等),池化的思想被广泛使用。 对象池的实现示例是非常简单直观的,直接看代码即可。 与之不同的是,使用C++实现一个实用的线程池或内存池的代码细节会更加复杂,本文中不作讨论。 示例代码如下 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#include <iostream>#include <memory>#include <vector>// 对象类class PooledObject {public: PooledO...
Cpp 设计模式笔记——5. 行为型模式(二)
Observer (观察者) 观察者模式是一种行为设计模式,在对象之间建立一种观察关系,使得在被观察对象的某个事件发生时,自动通知多个正在观察该对象的其他对象。 观察者模式的实现过程如下: 第一步,定义观察者基类Observer,它对外预留了update接口,代表更新观察者状态,被观察者将会通过调用这个接口来通知观察者; 第二步,定义被观察者基类Subject,它对外预留了几个接口: registerObserver:注册新的观察者,与之建立观察关系 removeObserver:移除指定的观察者,与之解出观察关系 notifyObservers:通知所有正在观察当前对象的观察者 第三步,定义具体的被观察者类(WeatherStation、StockMarket等),它们实际上在内部都需要使用一个观察者指针数组来记录所有注册的观察者,并且实现预留的三个接口,其中notifyObservers需要通知当前数组中的所有观察者。如果被观察者的状态被其它方法修改,需要主动触发notifyObservers来发出通知。 第四步,定义具体的观察者类(DisplayDevice、St...
Cpp 设计模式笔记——4. 行为型模式(一)
Chain of Responsibility (责任链) 责任链模式是一种行为设计模式,将请求沿着处理者链进行发送。在收到请求后,每个处理者均可选择对请求进行处理, 或将其继续传递给下个处理者。 首先我们需要创建一个抽象处理者基类HandlerBase,对外提供了几个方法: set_next:与另一个处理者建立后继关系,当前处理者可能把请求处理完成,也可能将请求传递给后继者; handle_request:尝试处理请求,为了避免在责任链中出现死循环,使用哈希表记录下曾经出现过的处理者,处理过程有三种结果: 遇到了可以处理请求的对象,处理成功; 回到之前遇到的处理者,表明出现死循环,处理失败; 责任链查找到了尽头,即不存在后继者,处理失败。 HandlerBase还预留了几个接口: can_handle:判断是否可以处理完成当前的请求,返回布尔值 handle:处理当前的请求 从基类HandlerBase继承得到各种类型的具体处理者(例如HandlerA、HandlerB和HandlerC), 它们分别实现了不同版本的can_handle和handle,不同类型的...