当前位置: 首页 > news >正文

【RAII | 设计模式】C++智能指针,内存管理与设计模式

前言

  • nav2系列教材,yolov11部署,系统迁移教程我会放到年后一起更新,最近年末手头事情多,还请大家多多谅解。
  • 上一节我们讲述了C++移动语义相关的知识,本期我们来看看C++中常用的几种智能指针,并看看他们在设计模式中的运用。
    • 【C++移动语义与完美转发】左值右值,引用,引用折叠,移动语义,万能引用与完美转发

1 RAII

请添加图片描述

  • RAII(Resource Acquisition Is Initialization)是一种广泛应用于 C++ 中的编程范式,特别是在资源管理和内存管理方面。RAII 的核心思想是资源的获取和释放与对象的生命周期绑定。资源的获取(如内存、文件句柄、网络连接等)通过构造函数完成,而资源的释放则通过析构函数完成。
1-1 RAII 的基本概念
  • 在 RAII 模式中,资源管理由对象的生命周期来控制。对象在创建时获取资源,在销毁时释放资源。这种方式有两个主要的好处:
    • 自动释放资源:对象的析构函数会自动释放资源,确保不会忘记释放。
    • 异常安全:由于析构函数会在对象生命周期结束时自动调用,它确保了即使发生异常,资源也能正确释放。
1-2 RAII 的工作原理
  • RAII 依赖于对象的构造和析构过程来管理资源,具体工作流程如下:
    1. 资源获取(Acquisition)
      • 在对象的构造函数中,获取资源。比如,打开一个文件、分配内存、获取数据库连接等。
    2. 资源释放(Release)
      • 在对象的析构函数中,释放资源。这样,当对象生命周期结束时,资源会自动被释放。
1-3 RAII 的优势
  • RAII 模式的最大优势就是自动管理资源。你不需要显式地调用资源释放函数(如 delete 或 close()),而是让 C++ 的对象生命周期来管理资源。
  • C++ 中的异常机制意味着代码在执行过程中可能会抛出异常。如果不小心在抛出异常之前忘记释放资源,可能会导致资源泄漏。RAII 模式通过将资源释放绑定到对象的析构函数来确保在任何情况下(包括异常发生时),资源都会被释放。
  • RAII 使得资源的管理更为简洁。你不需要显式调用释放资源的代码,只需要关注对象的生命周期,编译器和 C++ 标准库会自动处理资源的释放。

  • 而常见RAII的例子就是智能指针。

2 智能指针

2-1 介绍
  • 正如cppreference提到的,智能指针可以实现自动的、异常安全的、对象生命周期管理。在C++中,智能指针(Smart Pointers)是C++标准库提供的一种机制,用于自动管理动态分配的内存,减少内存泄漏和悬挂指针等问题。智能指针通过封装原始指针,提供了内存管理的自动化机制,即使程序员没有显式地调用 delete 来释放内存,智能指针也能保证内存最终会被正确释放。![[Pasted image 20241221100154.png]]
  • 在 C++11 中,确实引入了一些智能指针类型,来帮助管理对象的生命周期,减少内存泄漏和悬挂指针等问题。
    1. std::unique_ptr — 引入于 C++11,提供唯一所有权语义。
    2. std::shared_ptr — 引入于 C++11,提供共享所有权语义。
    3. std::weak_ptr — 引入于 C++11,用于解决 std::shared_ptr 之间的循环引用问题。
    4. std::auto_ptr — 在 C++98 中引入,在 C++11 中被废弃,并在 C++17 中移除。(本文不讲)
2-2 智能指针前置知识decltype
  • 这一小节我们会补充一些C++冷知识
  • decltype 是 C++11 引入的关键字,用于获取一个表达式的类型,或者说是推导某个对象、变量、表达式的类型。它非常强大,可以在编译时自动推导出类型,常用于模板编程或者自动推导复杂类型的场景。
  • decltype 的基本语法:
    • expression:可以是任何表达式,比如变量、函数调用、类型、运算结果等。
decltype(expression)
  • decltype 的作用
  1. 推导类型decltype 根据一个给定的表达式来推导出其类型。
int a=10;
decltype(a) b=a;
  1. auto 配合使用decltype 可以与 auto 搭配使用,用于推导复杂表达式的类型。
auto x = 10;            // auto 推导出 x 的类型是 int
decltype(x) y = 20;     // y 的类型也是 int
  1. 获取函数返回类型decltype 可以用于获取函数的返回类型,特别是在模板中,或者使用 auto 时,decltype 可以帮助我们推导出正确的类型。
template<typename T,typename U>
auto add(const T& a,const U&b)->decltype(a+b)
{return a+b;
}
  • 上述代码你可以会发现,即使不加decltype(a+b),auto也能正确地推倒出函数的正确类型,那么我们来看看下面的例子:
#include <iostream>
#include <vector>
#include <type_traits>template <typename T>
auto getElementAtIndex(const T& container, size_t index) {return container[index];  // 默认返回值是值类型
}template <typename T>
auto getElementAtIndexRef(T& container, size_t index) -> decltype(container[index])& {return container[index];  // 返回容器中元素的引用
}int main() {std::vector<int> vec = {10, 20, 30};auto element = getElementAtIndex(vec, 1);  // 返回的是值类型element=150;std::cout << "vec[1]: " << vec[1] << "\n";  // 20auto& elementRef = getElementAtIndexRef(vec, 1);  // 返回的是引用类型elementRef = 100; std::cout << "vec[1]: " << vec[1] << "\n";  // 100return 0;
}
  • 请添加图片描述

  • 可以看到decltype(container[index])& 指定为对应元素的引用类型


3 std::unique_ptr唯一所有权语义

3-1 基础语法
  • std::unique_ptr 是 C++11 引入的智能指针,它提供了独占式所有权的语义。也就是说,某个对象只能由一个 std::unique_ptr 拥有,且不能被拷贝,只能被转移。std::unique_ptr 是一种非常轻量的智能指针,适用于管理独占资源。
  • 特性
    • 唯一所有权std::unique_ptr 在任何时候只能有一个指针持有对象的所有权。如果你尝试复制一个 std::unique_ptr,会导致编译错误。你只能通过 std::move 来转移所有权。
    • 自动释放资源std::unique_ptr 会在超出作用域时自动释放所管理的对象。它的析构函数会调用对象的删除器(默认使用 delete)。
    • 不可复制std::unique_ptr 不支持拷贝构造和拷贝赋值,但支持移动构造和移动赋值。
    • 支持自定义删除器:可以为 std::unique_ptr 提供自定义删除器,指定如何释放所管理的对象。例如,可以使用文件关闭函数或自定义的内存释放策略。
  • 基础语法:
    • 备注:std::make_unique需要C++14,且std::make_unique 是推荐的创建 unique_ptr 的方法,避免了直接使用 new 带来的潜在问题。
    • (不推荐)如果非要使用C++11,你可以使用std::unique_ptr<T>(new T(...))
#include <memory>
class MyClass{};
std::unique_ptr<MyClass> ptr=std::make_unique<MyClass>();
3-2 特性分析
  • 移动语义std::unique_ptr 不支持复制,但支持移动:
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 转移所有权
// ptr1 现在是空指针,不能再使用
  • 释放和重置
    • reset() 可以释放当前所管理的对象,并且可以重新赋值新的对象:
    ptr1.reset();  // 释放当前管理的对象
    ptr1 = std::make_unique<MyClass>();  // 重新分配新对象
    
    • release() 可以获取当前管理的对象指针并释放所有权:
    MyClass* raw_ptr = ptr1.release();  // 获取裸指针,且ptr1不再管理该对象
    
  • 管理数组std::unique_ptr 也可以用于管理动态数组:
std::unique_ptr<MyClass[]> arr = std::make_unique<MyClass[]>(10);  // 管理一个长度为10的数组
  • 访问管理对象: 使用 get() 获取裸指针,使用 *-> 操作符访问对象:
std::unique_ptr<MyClass> ptr=std::make_unique<MyClass>();
MyClass* myclass=ptr.get();ptr->doSomething();  // 使用 -> 访问成员
(*ptr).doSomething();  // 使用 * 访问成员
3-3 自定义删除器
  • std::unique_ptr 支持自定义删除器,可以传递一个函数、lambda 或者函数对象来管理资源的释放:
auto deleter = [](MyClass* ptr) { std::cout << "Deleting MyClass\n"; delete ptr; };
std::unique_ptr<MyClass, decltype(deleter)> ptr(new MyClass(), deleter);
  • 自定义删除器reset() 函数的区别:
特性自定义删除器reset() 方法
目的控制资源的释放方式(不仅限于 delete手动释放当前对象并可重新分配新对象
使用场景需要自定义释放逻辑,例如文件关闭、内存管理等简单地手动释放资源或替换当前管理的对象
灵活性非常灵活,可以传入任何符合要求的删除器限制较少,简单但不支持额外的清理工作
适用情况需要自定义删除行为或清理逻辑只需要释放当前对象或重置 unique_ptr 为新对象
  • 这样我们就可以实现自动释放执行我们的自定义删除器了,实际运用中我们可以使用函数对象(function object)来封装自定义删除器
#include <iostream>
#include <memory>template<typename T>
struct CustonDeleter
{void operator()(T* ptr)const{// 进行额外的处理std::cout<<"CustonDeleter call"<<std::endl;delete ptr;}
};
class MyClass
{
public:MyClass(){std::cout<<"MyClass()"<<std::endl;}~MyClass(){std::cout<<"~MyClass()"<<std::endl;}};int main()
{std::unique_ptr<MyClass,CustonDeleter<MyClass>>ptr(new MyClass());return 0;
}
  • 请添加图片描述

3-4 std::unqiue_ptr在多态的使用
  • 代码如下
#include <iostream>
#include <memory>class Base
{
public:Base(){}virtual ~Base(){}virtual void run()=0;};
class A  :public Base
{
public:A(){std::cout<<"A()"<<std::endl;}~A(){std::cout<<"~A()"<<std::endl;}void run()override {std::cout<<"A run()"<<std::endl;}
};
class B  :public Base
{
public:B(){std::cout<<"B()"<<std::endl;}~B(){std::cout<<"~B()"<<std::endl;}void run()override{std::cout<<"B run()"<<std::endl;}
};
int main()
{std::unique_ptr<Base> ptr=std::make_unique<A>();ptr->run();ptr.reset();ptr=std::make_unique<B>();ptr->run();std::unique_ptr<Base> ptr2=std::move(ptr);ptr2->run();return 0;
}
  • 输出:

  • 请添加图片描述

  • 这样可以确保指针所拥有的对象不被拷贝。


4 std::shared_ptr共享所有权语义

4-1 基础语法
  • std::shared_ptr 是 C++11 引入的智能指针,它提供了共享所有权的语义。多个 std::shared_ptr 可以指向同一个对象,并且会通过引用计数来管理该对象的生命周期。当最后一个指向该对象的 shared_ptr 被销毁时,内存会被自动释放。
  • 特性
    • 共享所有权:多个 shared_ptr 可以共享对同一个对象的所有权。
    • 引用计数:每个 std::shared_ptr 都有一个引用计数,用来记录有多少个 shared_ptr 实例指向同一个对象。只有当最后一个引用被销毁时,才会删除该对象。
    • 自动内存管理:当最后一个 shared_ptr 被销毁时,自动释放资源。
  • 基础语法
    • use_count()会返回引用计数
#include <memory>
std::shared_ptr<MyClass>ptr=std::make_shared<MyClass>();
std::cout<<"ptr.use_count():"<<ptr.use_count()<<std::endl;
4-2 特性分析
  1. 引用计数与析构std::shared_ptr 使用引用计数来追踪有多少个 shared_ptr 对象共同拥有一个对象。当最后一个 shared_ptr 被销毁时,所管理的对象会自动析构。
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数变为 2
ptr1.reset(); // 引用计数减少到 1,ptr2 仍然拥有对象
  1. 访问管理的对象:使用 get() 方法可以获取裸指针,并且可以使用 * 和 -> 操作符来访问所管理的对象:
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
MyClass* raw_ptr = ptr.get(); // 获取裸指针ptr->doSomething();  // 使用 -> 访问成员
(*ptr).doSomething();  // 使用 * 访问成员
  1. 自定义删除器:std::shared_ptr 允许在创建时指定一个自定义的删除器,这个删除器会在最后一个 shared_ptr 被销毁时调用,以释放资源。
auto deleter = [](MyClass* ptr) {std::cout << "Deleting MyClass\n";delete ptr;
};std::shared_ptr<MyClass> ptr(new MyClass(), deleter);

4-3 std::enable_shared_from_this
  • std::enable_shared_from_this 是一个帮助类模板,允许对象获得指向自身的 shared_ptr。通常,类的成员函数可以通过 shared_from_this() 方法返回 shared_ptr,从而确保它在成员函数中正确地共享所有权。
#include <iostream>
#include <memory>class MyClass : public std::enable_shared_from_this<MyClass> {
public:MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destructed\n"; }void show() { std::cout << "MyClass::show() called\n"; }// 返回一个指向当前对象的 shared_ptrstd::shared_ptr<MyClass> getPtr() {return shared_from_this();}
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();std::shared_ptr<MyClass> ptr2 = ptr1->getPtr();ptr2->show();std::cout << "Use count: " << ptr1.use_count() << '\n';return 0;
}
4-4 std::shared_ptr 之间的循环引用问题
  • std::shared_ptr 是一种智能指针,通过引用计数的方式管理对象的生命周期。当一个 std::shared_ptr 对象被复制或赋值时,引用计数增加,直到没有任何 std::shared_ptr 持有对象时,才会删除对象。然而,在某些情况下,当多个 std::shared_ptr 相互引用时,可能会出现循环引用问题,导致内存泄漏。(明白多线程死锁的朋友们可以更理解这是啥)

    • 【C++并发入门】摄像头帧率计算和多线程相机读取(上):并发基础概念和代码实现请添加图片描述
  • 我们来看看这段代码

#include <iostream>
#include <memory>class A;
class B;class A {
public:std::shared_ptr<B> b_ptr;  // A 持有 B 的 shared_ptr~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::shared_ptr<A> a_ptr;  // B 持有 A 的 shared_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {// 创建 A 和 B 的 shared_ptr,并互相持有对方std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b;  // A 持有 Bb->a_ptr = a;  // B 持有 A// 退出作用域时,a 和 b 都不再使用,但由于循环引用,对象没有被销毁return 0;
}
  • 可以看到,程序执行后没有任何输出。这是因为 A 和 B 之间相互持有对方的 std::shared_ptr,导致引用计数永远不为零。

5 std::weak_ptr 解决 std::shared_ptr 之间的循环引用问题。

5-1 基础语法
  • std::weak_ptr 是 C++11 引入的智能指针,用来解决 std::shared_ptr 之间可能产生的循环引用问题。==weak_ptr 不增加引用计数,它是对由 std::shared_ptr 管理的对象的弱引用。==当 shared_ptr 对象被销毁时,weak_ptr 不会影响引用计数。
  • 特性
    • 临时所有权std::weak_ptr 模拟了临时所有权。对象可以被访问,但如果对象被销毁,std::weak_ptr 仍然不持有对对象的所有权,并且不增加引用计数。只有在将 std::weak_ptr 转换为 std::shared_ptr 时,才能获得对象的所有权,并且此时对象的生命周期会延长,直到该 shared_ptr 被销毁。
    • 打破循环引用std::weak_ptr 是避免由 std::shared_ptr 引起的循环引用问题的常用方式。循环引用是由于对象相互持有 shared_ptr,使得引用计数永远不为零,从而导致内存泄漏。通过将其中一个对象的 shared_ptr 转为 weak_ptr,可以打破这种循环,避免内存泄漏。
  • 基础语法
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp(sp);  // 创建一个 weak_ptr,指向 shared_ptr 管理的对象
5-2 特性
  1. lock()lock() 函数返回一个 std::shared_ptr,它可以安全地访问 weak_ptr 引用的对象。如果原始 std::shared_ptr 已被销毁,lock() 会返回一个空的 shared_ptr。
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;
std::shared_ptr<int> spt = wp.lock();  // 获取 shared_ptrif (spt) {std::cout << "Object value: " << *spt << std::endl;
} else {std::cout << "Object expired." << std::endl;
}
  1. use_count():use_count() 返回当前有多少个 std::shared_ptr 管理该对象的引用。注意,std::weak_ptr 不会增加引用计数。
std::cout << "use_count() == " << wp.use_count() << std::endl;  // 输出引用计数
  1. expired():expired() 函数返回一个布尔值,表示 std::weak_ptr 所引用的对象是否已经被销毁(即对应的 std::shared_ptr 已经被销毁)。
if (wp.expired()) {std::cout << "The object has been destroyed." << std::endl;
} else {std::cout << "The object is still alive." << std::endl;
}
  1. reset():reset() 可以清除 std::weak_ptr 对对象的引用。
wp.reset();  // 重置 weak_ptr,释放引用
5-3 解决 std::shared_ptr 之间的循环引用问题。
  • 我们看看刚才那个代码
#include <iostream>
#include <memory>class A;
class B;class A {
public:std::weak_ptr<B> b_ptr;  // A 持有 B 的 weak_ptr,避免循环引用~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::shared_ptr<A> a_ptr;  // B 持有 A 的 shared_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {// 创建 A 和 B 的 shared_ptr,并互相持有对方std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b;  // A 持有 Bb->a_ptr = a;  // B 持有 A// 退出作用域时,a 和 b 都会被销毁return 0;
}
  • 请添加图片描述

  • 上述修改后的代码,AB 之间通过 std::shared_ptr 互相持有对方。为了避免循环引用,我们将 A 中的 B 指针改为 std::weak_ptr<B>。这样,AB 的引用不会增加 B 的引用计数,从而打破了循环引用。当作用域结束时,AB 都会被正确销毁。

5-4 std::weak_ptrstd::shared_ptr 的协作
  • std::weak_ptr 的使用场景通常是与 std::shared_ptr 配合。你可以通过 std::weak_ptr 来观察一个由 std::shared_ptr 管理的对象,并且在需要时通过调用 lock() 来获得 shared_ptr,确保对象的生命周期被正确延长,直到临时的 shared_ptr 被销毁。
#include <iostream>
#include <memory>std::weak_ptr<int> gw;  // 定义一个 weak_ptrvoid observe() {std::cout << "gw.use_count() == " << gw.use_count() << "; ";if (auto spt = gw.lock()) {  // 使用 lock() 获得 shared_ptrstd::cout << "*spt == " << *spt << '\n';} else {std::cout << "gw is expired\n";}
}int main() {{auto sp = std::make_shared<int>(42);  // 创建 shared_ptrgw = sp;  // 将 shared_ptr 转换为 weak_ptrobserve();  // 调用 observe 函数,输出对象的值}observe();  // 再次调用 observe,检查 weak_ptr 是否有效
}
  • 请添加图片描述

6 智能指针和设计模式

6-1 设计模式

在这里插入图片描述

  • 设计模式(Design Patterns)是软件设计领域中一套通用的、被反复验证的解决方案,用于解决软件开发中常见的问题。设计模式提供了一些经过验证的、优雅的代码结构,帮助开发者在面对某些重复问题时,能够采用一种标准化的解决方案,从而提高代码的可维护性、可扩展性和可重用性。
  • 设计模式分为 创建型结构型行为型 三大类,每一类中都包含了一些常见的模式。
6-1单例模式(Singleton Pattern)与智能指针
  • 单例模式旨在确保某个类只有一个实例,并提供全局访问点。在 C++ 中,使用智能指针(特别是 std::unique_ptr)来实现单例模式,可以避免手动管理对象生命周期,确保对象的销毁由智能指针自动处理。
#include <iostream>
#include <memory>class Singleton {
private:static std::unique_ptr<Singleton> instance;// 私有化构造函数,防止外部创建对象Singleton() { std::cout << "Singleton instance created!" << std::endl; }public:// 禁用拷贝构造和赋值操作Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 获取单例实例static Singleton* getInstance() {if (!instance) {instance = std::make_unique<Singleton>();}return instance.get();}void showMessage() {std::cout << "Hello from Singleton!" << std::endl;}
};// 静态成员初始化
std::unique_ptr<Singleton> Singleton::instance = nullptr;int main() {Singleton::getInstance()->showMessage();return 0;
}
  • 使用 std::unique_ptr 管理单例对象的生命周期,确保在程序结束时,单例对象会自动销毁。
  • getInstance() 方法使用懒加载(Lazy Initialization)方式,只有在需要时才创建单例对象。

6-3 工厂方法模式(Factory Method Pattern)与智能指针
  • 工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪个类。使用智能指针,特别是 std::unique_ptrstd::shared_ptr,可以管理对象的生命周期,同时避免了内存泄漏问题。
#include <iostream>
#include <memory>// 产品接口
class Product {
public:virtual void use() = 0;virtual ~Product() = default;
};// 具体产品
class ConcreteProductA : public Product {
public:void use() override {std::cout << "Using ConcreteProductA" << std::endl;}
};class ConcreteProductB : public Product {
public:void use() override {std::cout << "Using ConcreteProductB" << std::endl;}
};// 工厂基类
class Creator {
public:virtual std::unique_ptr<Product> factoryMethod() = 0;virtual ~Creator() = default;
};// 具体工厂
class ConcreteCreatorA : public Creator {
public:std::unique_ptr<Product> factoryMethod() override {return std::make_unique<ConcreteProductA>();}
};class ConcreteCreatorB : public Creator {
public:std::unique_ptr<Product> factoryMethod() override {return std::make_unique<ConcreteProductB>();}
};int main() {std::unique_ptr<Creator> creatorA = std::make_unique<ConcreteCreatorA>();std::unique_ptr<Product> productA = creatorA->factoryMethod();productA->use();std::unique_ptr<Creator> creatorB = std::make_unique<ConcreteCreatorB>();std::unique_ptr<Product> productB = creatorB->factoryMethod();productB->use();return 0;
}
  • 使用 std::unique_ptr 来确保工厂方法返回的对象的生命周期得到了妥善管理,无需手动删除。
  • 在创建对象时,工厂方法会返回一个智能指针,从而保证了内存的自动释放。

6-4 观察者模式(Observer Pattern)与智能指针
  • 观察者模式允许一个对象(被观察者)通知多个依赖对象(观察者)状态的变化。使用 std::shared_ptr 可以确保观察者对象在多个观察者之间共享,并且通过引用计数管理生命周期。
#include <iostream>
#include <vector>
#include <memory>// 抽象观察者
class Observer {
public:virtual void update(int value) = 0;virtual ~Observer() = default;
};// 被观察者
class Subject {
private:std::vector<std::shared_ptr<Observer>> observers;public:void addObserver(const std::shared_ptr<Observer>& observer) {observers.push_back(observer);}void removeObserver(const std::shared_ptr<Observer>& observer) {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void notifyObservers(int value) {for (auto& observer : observers) {observer->update(value);}}
};// 具体观察者
class ConcreteObserver : public Observer {
private:std::string name;public:ConcreteObserver(const std::string& name) : name(name) {}void update(int value) override {std::cout << "Observer " << name << " received value: " << value << std::endl;}
};int main() {Subject subject;// 创建观察者auto observer1 = std::make_shared<ConcreteObserver>("A");auto observer2 = std::make_shared<ConcreteObserver>("B");subject.addObserver(observer1);subject.addObserver(observer2);// 通知观察者subject.notifyObservers(42);return 0;
}
  • 观察者使用 std::shared_ptr 管理,确保所有观察者对象的生命周期由引用计数自动管理。
  • 当观察者被移除时,std::shared_ptr 会自动销毁对象,避免内存泄漏。

6-5 策略模式(Strategy Pattern)与智能指针
  • 策略模式允许在运行时选择算法或行为。使用智能指针(如 std::shared_ptrstd::unique_ptr)可以简化策略对象的创建和管理,避免手动内存管理。
#include <iostream>
#include <memory>// 策略接口
class Strategy {
public:virtual void execute() const = 0;virtual ~Strategy() = default;
};// 具体策略A
class ConcreteStrategyA : public Strategy {
public:void execute() const override {std::cout << "Executing Strategy A" << std::endl;}
};// 具体策略B
class ConcreteStrategyB : public Strategy {
public:void execute() const override {std::cout << "Executing Strategy B" << std::endl;}
};// 上下文类
class Context {
private:std::shared_ptr<Strategy> strategy;public:void setStrategy(const std::shared_ptr<Strategy>& newStrategy) {strategy = newStrategy;}void executeStrategy() {if (strategy) {strategy->execute();}}
};int main() {Context context;// 使用策略Acontext.setStrategy(std::make_shared<ConcreteStrategyA>());context.executeStrategy();// 使用策略Bcontext.setStrategy(std::make_shared<ConcreteStrategyB>());context.executeStrategy();return 0;
}
  • 策略对象通过 std::shared_ptr 管理,确保不同策略的生命周期由智能指针自动控制。
  • 在不同的策略之间切换时,智能指针确保不需要手动管理内存。

6-6代理模式(Proxy Pattern)与智能指针
  • 代理模式为其他对象提供一个代理对象,以控制对实际对象的访问。智能指针可以帮助管理代理对象和实际对象的生命周期,确保资源的正确释放。
#include <iostream>
#include <memory>// 抽象Subject
class Subject {
public:virtual void request() = 0;virtual ~Subject() = default;
};// 具体Subject
class RealSubject : public Subject {
public:void request() override {std::cout << "RealSubject request" << std::endl;}
};// 代理类
class Proxy : public Subject {
private:std::shared_ptr<RealSubject> realSubject;public:Proxy(std::shared_ptr<RealSubject> realSubject) : realSubject(realSubject) {}void request() override {std::cout << "Proxy forwarding request to RealSubject" << std::endl;realSubject->request();}
};int main() {auto realSubject = std::make_shared<RealSubject>();Proxy proxy(realSubject);proxy.request();return 0;
}
  • std::shared_ptr 用于管理 RealSubject 的生命周期,确保它在 Proxy 使用时依然有效,并在不再需要时自动销毁。
  • 代理类 Proxy 可以控制对实际对象的访问,比如增加日志、访问控制等功能。

7 总结

  • RAII 是 C++ 中一项非常重要的编程理念,尤其适用于资源管理、内存管理以及多线程编程。在 C++11 及之后的版本中,智能指针(如 std::unique_ptr, std::shared_ptr, 和 std::weak_ptr)为RAII 模式提供了强大的支持,能够自动管理资源,确保资源在对象生命周期结束时得到正确释放。
    • RAII 的优点:通过绑定资源的获取与释放到对象的生命周期,简化了资源管理,减少了错误发生的可能,增强了异常安全性。
    • 智能指针的优势:通过 std::unique_ptr, std::shared_ptr, std::weak_ptr 等智能指针,C++ 提供了自动化的内存管理,避免了常见的内存泄漏和悬挂指针问题。
    • RAII 与异常安全:RAII 使得在发生异常时,资源能够被正确地释放,从而保证程序的稳定性。
  • 如有错误,欢迎指出!
  • 感谢大家的支持

相关文章:

【RAII | 设计模式】C++智能指针,内存管理与设计模式

前言 nav2系列教材&#xff0c;yolov11部署,系统迁移教程我会放到年后一起更新&#xff0c;最近年末手头事情多&#xff0c;还请大家多多谅解。 上一节我们讲述了C移动语义相关的知识&#xff0c;本期我们来看看C中常用的几种智能指针&#xff0c;并看看他们在设计模式中的运…...

亚马逊云科技re:Invent:2025年将发生新变化

自从2006年推出Simple Storage Service&#xff08;S3&#xff09;和Elastic Compute Cloud&#xff08;EC2&#xff09;云计算服务以来&#xff0c;亚马逊云科技在过去的18年中&#xff0c;一直都是全球云计算技术的开创者和引领者。而随着人工智能技术的飞速发展和生成式AI时…...

某集团GIF动态验证码识别

注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路 如有侵犯,请联系作者下架 本文识别已同步上线至OCR识别网站: http://yxlocr.nat300.top/ocr/other/16 最近某集团更新了验证码,采用gif验证码,部分数据集展示如下...

llama.cpp:PC端测试 MobileVLM -- 电脑端部署图生文大模型

llama.cpp&#xff1a;PC端测试 MobileVLM 1.环境需要2.构建项目3.PC测试 1.环境需要 以下是经实验验证可行的环境参考&#xff0c;也可尝试其他版本。 &#xff08;1&#xff09;PC&#xff1a;Ubuntu 22.04.4 &#xff08;2&#xff09;软件环境&#xff1a;如下表所示 工…...

美国加州房价数据分析01

1.项目简介 本数据分析项目目的是分析美国加州房价数据&#xff0c;预测房价中值。 环境要求&#xff1a; ancondajupyter notebookpython3.10.10 虚拟环境&#xff1a; pandas 2.1.1 numpy 1.26.1 matplotlib 3.8.0 scikit-learn1.3.1 2. 导入并探索数据集 通用的数据分析…...

聚类算法DBSCAN 改进总结

目录 1. HDBSCAN (Hierarchical DBSCAN) 2. OPTICS (Ordering Points To Identify the Clustering Structure) 3. DBSCAN++ (DBSCAN with Preprocessing) 4. DBSCAN with k-distance 5. Density Peaks Clustering (DPC) 6. Generalized DBSCAN (GDBSCAN) 总结 是的,DBS…...

深入理解 Spring IoC 容器与依赖注入:从基本概念到高级应用的全面解析

IoC 容器与依赖注入 一、什么是IoC容器二、IoC原理1. 原理解释2. 一个通俗易懂的解释3. 举个例子a. 传统方式:手动创建对象b. IoC 和 DI:控制反转与依赖注入c. 解释d.总结三、依赖注入(DI)的三种方式1. 构造器注入(Constructor Injection)2. 字段注入(Field Injection)…...

什么是自我控制能力?如何提高自我控制能力?

什么是自我控制能力&#xff1f; 自我控制能力指&#xff0c;在遇到外在事物或者心理活动发生变化之时&#xff0c;人们仍然可以把握自身&#xff0c;指导接下来行动的能力。自我控制能力对一个人来说非常重要&#xff0c;因为在遇到一些事情之事&#xff0c;如果因为控制能力…...

【基于rust-wasm的前端页面转pdf组件和示例】

基于rust-wasm前端页面转pdf组件和示例 朔源多余的废话花哨的吹牛那点东西要不要拿来试试事到如今 做个美梦 我觉得本文的意义在于,wasm扩展了浏览器的边界,但是又担心如同java的web applet水土不服. 如同我至今看不出塞班和iOS的不同下载地址&#xff1a;在github的备份 朔源…...

Issac ROS navigation测试

软硬件环境 GPU: RTX 6000 Ada 系统&#xff1a; Ubuntu22.4 1. 启动Issac sim 从这个网页Develop on NVIDIA Omniverse Platform | NVIDIA Developer下载Omniverse Launcher&#xff0c; 然后执行./omniverse-launcher-linux.AppImage&#xff0c;从EXCHANGE里依次安装Isa…...

WWW23-多行为级联|级联图卷积网络的多行为推荐

论文&#xff1a;https://arxiv.org/abs/2303.15720 代码&#xff1a;https://github.com/SS-00-SS/MBCGCN 这篇论文MB-CGCN和上一篇CRGCN是同一个团队的&#xff0c;都是级联的方式。一个用了残差&#xff0c;一个用了特征转换&#xff0c;文章最后有discussion讨论了两者的不…...

实力认可 | 通付盾入选《ISC.AI 2024创新能力全景图谱》五项领域

近日&#xff0c;ISC.AI 2024创新能力百强&#xff08;以下简称“创新百强”&#xff09;正式发布《ISC.AI 2024创新能力全景图谱》。该全景图谱是由政企、资本、高校、行业力量共同完成了领域划分、综合创新等标准的制定&#xff0c;整合梳理了参评的300余家数字安全厂商、120…...

『Linux学习笔记』FRPC 详细介绍及配置解析!

『Linux学习笔记』FRPC 详细介绍及配置解析&#xff01; 文章目录 一. FRPC 详细介绍及配置解析FRPC 的主要功能FRPC 配置文件解析全局配置代理配置第一个代理服务第二个代理服务 配置文件整体工作流程常见配置项说明FRPC 的使用步骤注意事项结论 二. 参考文献 一. FRPC 详细介…...

JS信息收集(小迪网络安全笔记~

免责声明&#xff1a;本文章仅用于交流学习&#xff0c;因文章内容而产生的任何违法&未授权行为&#xff0c;与文章作者无关&#xff01;&#xff01;&#xff01; 附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;…...

nmap扫描优化

扫描优化用来提高扫描效率。当描一个大范围网络中的主机时&#xff0c;如果使用通用的方法可能需要很长的时间&#xff0c;此时可以使用一些特定选项进行扫描优化&#xff0c;以提高扫描效率。Nmap提供了几种优化方式&#xff0c;如分组扫描、设置发包方式和超时时间等。 分组…...

Nautilus源码编译傻瓜式教程二

Nautilus源码编译傻瓜式教程一 Nautilus编译 依赖项文件 接上文,点击小锤子进行编译后出现如下的错误提示 看这个报错,未找到文件或目录,再看前面的git地址是github就知道肯定是下载有问题,查找下Nautilus项目,发现在nautilus/build-aux/flatpak/org.gnome.Nautilus.json文件…...

并发编程(19)——引用计数型无锁栈

文章目录 十九、day191. 引用计数2. 代码实现2.1 单引用计数器无锁栈2.2 双引用计数器无锁栈 3. 本节的一些理解 十九、day19 上一节我们学习通过侯删链表以及风险指针与侯删链表的组合两种方式实现了并发无锁栈&#xff0c;但是这两种方式有以下缺点&#xff1a; 第一种方式…...

Santa Claus 2 (st表的lower_bound用法)

题目链接&#xff1a;Santa Claus 2 #pragma GCC optimize(2) #include <bits/stdc.h> #define int long long #define fi first #define se second #define all(v) v.begin(),v.end() using namespace std; const int inf 0x3f3f3f3f3f3f3f; const int N 2e55; int …...

Reed-Muller(RM)码之编码

点个关注吧! 看了一些中文的博客,RM码没有很详细的资料,所以本文尝试给出推导原理。 推导 RM码由 ( r , m ) ( r , m ) (r,m)两个参数定义,记作 R M ( r , m ) RM(r,m) RM(r,m)。其中满足 0 ≤ r ≤ m 0 ≤ r ≤ m 0≤r≤m,含义为: 码长: n = 2 m n=2^m n=2m 维数:…...

新世纪的语言智能:GPT-5技术革新与市场前景展望

目录 引言 第一章&#xff1a;GPT-4的成就与局限 1.1 GPT-4的成功 1.2 GPT-4的局限性 第二章&#xff1a;对GPT-5技术革新的预测 2.1 增强的上下文理解能力 2.2 多模态能力的提升 2.3 创造力与多样性的增强 2.4 常识性知识与伦理性的提升 第三章&#xff1a;GPT-5的市…...

国高材服务 | 高分子结晶动力学表征——高低温热台偏光显微镜

众所周知&#xff0c;聚合物制品的实际使用性能&#xff08;如光学透明性、硬度、模量等&#xff09;与材料内部的结晶形态、晶粒大小及完善程度有着密切的联系&#xff0c;因此&#xff0c;对聚合物结晶形态等的研究具有重要的理论和实际意义。 随着结晶条件的不用&#xff0c…...

python+PyPDF2实现PDF的文本内容读取、多文件合并、旋转、裁剪、缩放、加解密、添加水印

目录 读取内容 合并文件 旋转 缩放 裁剪 加密和解密 添加水印 安装&#xff1a;pip install PyPDF2 -i https://pypi.tuna.tsinghua.edu.cn/simple 读取内容 from PyPDF2 import PdfReader, PdfMerger, PdfWriterdef read_pdf(pdf_path):pdf_reader PdfReader(pdf_p…...

蓝桥杯物联网开发板硬件组成

第一节 开发板简介 物联网设计与开发竞赛实训平台由蓝桥杯大赛技术支持单位北京四梯科技有限公司设计和生产&#xff0c;该产品可用于参加蓝桥杯物联网设计与开发赛道的竞赛实训或院校相关课程的 实践教学环节。 开发板基于STM32WLE5无线微控制器设计&#xff0c;芯片提供了25…...

idea2024创建JavaWeb项目以及配置Tomcat详解

今天呢&#xff0c;博主的学习进度也是步入了JavaWeb&#xff0c;目前正在逐步杨帆旗航&#xff0c;迎接全新的狂潮海浪。 那么接下来就给大家出一期有关JavaWeb的配置教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正…...

【蓝桥杯每日一题】分糖果——DFS

分糖果 蓝桥杯每日一题 2024-12-24 分糖果 DFS 题目描述 两种糖果分别有 9 个和 16 个&#xff0c;要全部分给 7 个小朋友&#xff0c;每个小朋友得到的糖果总数最少为 2 个最多为 5 个&#xff0c;问有多少种不同的分法。糖果必须全部分完。 只要有其中一个小朋友在两种方案中…...

矩阵在资产收益(Asset Returns)中的应用:以资产回报矩阵为例(中英双语)

本文中的例子来源于&#xff1a; 这本书&#xff0c;网址为&#xff1a;https://web.stanford.edu/~boyd/vmls/ 矩阵在资产收益(Asset Returns)中的应用&#xff1a;以资产回报矩阵为例 在量化金融中&#xff0c;矩阵作为一种重要的数学工具&#xff0c;被广泛用于描述和分析…...

Jimureport h2命令执行分析记录

首先找testConnection接口&#xff0c;前面进行了jimureport-spring-boot-starter-1.5.8.jar反编译查找&#xff0c;接口找到发现请求参数是json var1是JmreportDynamicDataSourceVo类型&#xff0c;也就是如上图的dbSource&#xff0c;根据打印的结果可以知道这里是local cac…...

1114 Family Property (25)

This time, you are supposed to help us collect the data for family-owned property. Given each persons family members, and the estate&#xff08;房产&#xff09;info under his/her own name, we need to know the size of each family, and the average area and n…...

OpenEuler 22.03 安装 flink-1.17.2 集群

零&#xff1a;规划 本次计划安装三台OpenEuler 22.03 版本操作系统的服务器&#xff0c;用于搭建 flink 集群。这里使用flink1.17.2 的原因&#xff0c;是便于后续与springboot的整合 服务器名IP地址作用其他应用flink01192.168.159.133主jdk11、flink-1.17.2flink02192.168.…...

SQL—leetcode—175. 组合两个表

175. 组合两个表 表: Person -------------------- | 列名 | 类型 | -------------------- | PersonId | int | | FirstName | varchar | | LastName | varchar | -------------------- personId 是该表的主键&#xff08;具有唯一值的列&#xff09;。 该表包含一些人的 ID 和…...

html 中 表格和表单的关系与区别

在 HTML 中&#xff0c;表格 (<table>) 和表单 (<form>) 是两种常用于展示数据和收集用户输入的元素。它们具有不同的功能和结构。以下是关于这两者的详细介绍&#xff1a; 1. HTML 表格&#xff08;<table>&#xff09; 表格用于展示结构化的数据&#xf…...

Android14 OTA升级速度过慢问题解决方案

软件版本&#xff1a;Android14 硬件平台&#xff1a;QCS6115 问题&#xff1a;OTA整包升级接近20min&#xff0c;太长无法忍受。 该问题为Android高版本的虚拟AB分区压缩技术所致&#xff0c;其实就是时间换空间&#xff0c;个人推测AB分区压缩会节约硬件存储空间&#xff0…...

Jetson xavier 刷机安装教程

在对Jetson进行刷机过程&#xff0c;浏览了很多的相关教程&#xff0c;大部分教程并不全&#xff0c;而且按照步骤执行会出现许多奇奇怪怪的错误&#xff0c;为了避免大家踩坑&#xff0c;这里给出了完整的解决方法&#xff0c;希望能够提供帮助&#xff01; 首先大家需要准备…...

Hadoop集群(HDFS集群、YARN集群、MapReduce​计算框架)

一、 简介 Hadoop主要在分布式环境下集群机器&#xff0c;获取海量数据的处理能力&#xff0c;实现分布式集群下的大数据存储和计算。 其中三大核心组件: HDFS存储分布式文件存储、YARN分布式资源管理、MapReduce分布式计算。 二、工作原理 2.1 HDFS集群 Web访问地址&…...

芯科科技蓝牙、Wi-Fi、Wi-SUN产品广获业界认可,技术创新引领行业潮流

物联网领军企业领跑未来无线开发平台发展 2024年&#xff0c;Silicon Labs&#xff08;亦称“芯科科技“&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;在物联网&#xff08;IoT&#xff09;领域持续深耕&#xff0c;凭借创新的企业发展理念与实践、行业领先的技术与产品&am…...

C语言——数据在内存中的存储

目录 前言 一数据类型 类型归类 二整形在内存中的存储 原反补码 大小端 相关练习题 三浮点数在内存中的储存 浮点数储存规则 前言 只有取学习数据在内存中的存储&#xff0c;我们在以后才能定义好&#xff08;用好&#xff09;各种类型的数据&#xff01; 一数据类型…...

后端-redis

Redis RedisString类型String类型的常用命令 Hash类型Hash类型的常用命令 List类型List类型的常用命令 Set类型Set类型的常用命令 SortedSet类型SortedSet类型的常用命令 Redis序列化缓存更新策略缓存穿透缓存雪崩缓存击穿 Redis Redis是一个key-value的数据库&#xff0c;key…...

sqoop,flume草稿

连xftp传sqoop压缩包到/opt/soft 目录下 cd opt/soft/ tar -zxvf sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz mv sqoop-1.4.7.bin__hadoop-2.6.0 sqoop cd sqoop/conf/ cp sqoop-env-template.sh sqoop-env.sh vi sqoop-env-sh export HADOOP_COMMON_HOME/opt/soft/hadoop expo…...

UE5 渲染管线 学习笔记

兰伯特 SSS为散射的意思 带Bias的可以根据距离自动切换mip的卷积值 而带Level的值mipmaps的定值 #define A8_SAMPLE_MASK .a 这样应该就很好理解了 这个只采样a通道 带Level的参考上面的 朝左上和右下进行模糊 带Bias参考上面 随机数 4D 3D 2D 1D HLSL内置UV HLSL内置鼠标坐…...

线程池使用不当导致线程死锁

线程池使用不当导致线程死锁 问题代码问题分析 问题代码 在项目开发中&#xff0c;为了支持并发场景&#xff0c;减少资源开销&#xff0c;通常会使用公共线程池&#xff0c;即预先创建一个线程池&#xff0c;需要并发时都将任务提交该线程池中。类似如下代码 public class T…...

SpringBoot状态机

Spring Boot 状态机&#xff08;State Machine&#xff09;是 Spring Framework 提供的一种用于实现复杂业务逻辑的状态管理工具。它基于有限状态机&#xff08;Finite State Machine, FSM&#xff09;的概念&#xff0c;允许开发者定义一组状态、事件以及它们之间的转换规则。…...

细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV

目录 一、工程配置 1、时钟、DEBUG 2、GPIO 3、SPI2 4、USART6 5、NVIC 二、软件设计 1、FALSH &#xff08;1&#xff09;w25flash.h &#xff08;2&#xff09; w25flash.c 1&#xff09;W25Q16基本操作指令 2&#xff09;计算地址的辅助功能函数 3&#xff09;器…...

HTMLCSS:惊!3D 折叠按钮

这段代码创建了一个具有 3D 效果和动画的按钮&#xff0c;按钮上有 SVG 图标和文本。按钮在鼠标悬停时会显示一个漂浮点动画&#xff0c;图标会消失并显示一个线条动画。这种效果适用于吸引用户注意并提供视觉反馈。按钮的折叠效果和背景渐变增加了页面的美观性。 演示效果 HT…...

如何更好的进行时间管理

先想一下我们想要做的事情&#xff0c;然后拿出Excel表格将这些事情记录下来&#xff0c;我们把它叫做任务对这些任务按照重要性&#xff0c;紧急程度进行排序&#xff0c;拿出表格中的前六个任务&#xff0c;就是今天要做的任务新建另一张excel表格&#xff0c;表格的一列为时…...

我在华为的安全日常

在华为工作了数年后&#xff0c;我养成了一个习惯&#xff1a;每次离开座位&#xff0c;即便是去卫生间&#xff0c;我也会条件反射地锁屏电脑。晚上回到家&#xff0c;躺在床上&#xff0c;脑海中偶尔会闪过一丝疑虑&#xff1a;办公室的门窗是否关好&#xff1f;虽然这种担忧…...

for媒体打破智能座舱体验同质化,斑马智行荣获“华舆奖”优秀创

打破智能座舱体验同质化&#xff0c;斑马智行荣获“华舆奖”优秀创新生态伙伴 12月12日&#xff0c;消费者洞察与市场研究机构J.D. Power|君迪与同济大学 HVR Lab&#xff08;人车关系实验室&#xff09;共同发布了 2024 中国智能座舱的研究洞察&#xff0c;并公布了华舆奖中国…...

自己搭建专属AI:Llama大模型私有化部署

前言 AI新时代&#xff0c;提高了生产力且能帮助用户快速解答问题&#xff0c;现在用的比较多的是Openai、Claude&#xff0c;为了保证个人隐私数据&#xff0c;所以尝试本地&#xff08;Mac M3&#xff09;搭建Llama模型进行沟通。 Gpt4all 安装比较简单&#xff0c;根据 G…...

芯片Tapeout power signoff 之IR Drop Redhawk Ploc文件格式及其意义

数字IC后端工程师在芯片流程最后阶段都会使用redhawk或voltus进行设计的IR Drop功耗signoff分析。必须确保静态&#xff0c;动态ir drop都符合signoff标准。 在做redhawk ir drop分析前&#xff0c;我们需要提供一个redhawk ploc供电点坐标。 数字IC设计后端实现前期预防IR D…...

[机器学习]sklearn入门指南(1)

简介 scikit-learn&#xff08;简称sklearn&#xff09;是一个开源的Python机器学习库&#xff0c;它提供了简单而高效的工具用于数据挖掘和数据分析&#xff0c;并且拥有一个活跃的开发社区。它建立在NumPy、SciPy和matplotlib这些科学计算库之上&#xff0c;旨在提供一致且可…...

GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台

GoIoT 是基于Gin 的开源分布式物联网&#xff08;IoT&#xff09;开发平台&#xff0c;用于快速开发&#xff0c;部署物联设备接入项目&#xff0c;是一套涵盖数据生产、数据使用和数据展示的解决方案。 GoIoT 开发平台&#xff0c;它是一个企业级物联网平台解决方案&#xff…...