【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 使得在发生异常时,资源能够被正确地释放,从而保证程序的稳定性。
  • 如有错误,欢迎指出!
  • 感谢大家的支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65178.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软 CEO 萨提亚・纳德拉:回顾过去十年,展望 AI 时代的战略布局

近日&#xff0c;微软 CEO 萨提亚・纳德拉与著名投资人比尔・格里和布拉德・格斯特纳进行了一场深度对话&#xff0c;回顾了过去十年微软的转型历程&#xff0c;并展望了 AI 时代的战略布局。在这次访谈中&#xff0c;纳德拉分享了他在微软的早期经历&#xff0c;包括他加入微软…

【Java-tesseract】OCR图片文本识别

文章目录 一、需求二、概述三、部署安装四、技术细节五、总结 一、需求 场景需求:是对识别常见的PNG,JPEG,TIFF,GIF图片识别&#xff0c;环境为离线内网。组件要求开源免费&#xff0c;并且可以集成Java生成接口服务。 二、概述 我不做选型对比了,我筛选测试了下Tesseract(v…

iOS开发代码块-OC版

iOS开发代码块-OC版 资源分享资源使用详情Xcode自带代码块自定义代码块 资源分享 自提&#xff1a; 通过网盘分享的文件&#xff1a;CodeSnippets 2.zip 链接: https://pan.baidu.com/s/1Yh8q9PbyeNpuYpasG4IiVg?pwddn1i 提取码: dn1i Xcode中的代码片段默认放在下面的目录中…

如何借助边缘智能网关实现厂区粉尘智能监测告警

在诸如木制品加工、纺织品加工、塑料橡胶制品加工等多种工业生产场景中&#xff0c;粉尘问题的隐患和风险不可小觑。如果缺少对生产环境中粉尘的监测和管理&#xff0c;可能发生易燃易爆、环境污染和工人尘肺等生产事故。 针对工业场景中的粉尘状况监测、管理及预警&#xff0c…

McDonald‘s Event-Driven Architecture 麦当劳事件驱动架构

原文链接 1 mcdonalds-technical-blog/ 原文链接 2 mcdonalds-technical-blog/ 麦当劳在异步、事务性和分析性处理用例中使用跨技术栈的事件&#xff0c;包括移动订单进度跟踪和向客户发送营销通信&#xff08;交易和促销&#xff09;。 统一事件平台&#xff08;unified eve…

EasyExcel停更,FastExcel接力

11月6日消息&#xff0c;阿里巴巴旗下的Java Excel工具库EasyExcel近日宣布&#xff0c;将停止更新&#xff0c;未来将逐步进入维护模式&#xff0c;将继续修复Bug&#xff0c;但不再主动新增功能。 EasyExcel以其快速、简洁和解决大文件内存溢出的能力而著称&#xff0c;官方…

HarmonyOS NEXT 实战之元服务:静态多案例效果(一)

背景&#xff1a; 前几篇学习了元服务&#xff0c;后面几期就让我们开发简单的元服务吧&#xff0c;里面丰富的内容大家自己加&#xff0c;本期案例 仅供参考 先上本期效果图 &#xff0c;里面图片自行替换 效果图1代码案例如下&#xff1a; import { authentication } from…

前端(Ajax)

1.客户端请求 向https://jsonplaceholder.typicode.com/users发送get请求 const xhr new XMLHttpRequest(); console.log(xhr.readyState); xhr.open(‘get’, ‘https://jsonplaceholder.typicode.com/users’) console.log(xhr.readyState); xhr.send(); console.log(xhr.…

java高频面试之SE-05

面试官&#xff1a;java中为什么有多态&#xff1f; 面试官你好&#xff01;Java 中有多态主要是为了实现灵活性和可扩展性。通过多态&#xff0c;可以用统一的接口处理不同的对象&#xff0c;从而提高代码的可维护性和可复用性。以下是多态的几个关键原因&#xff1a; 1. 代…

DP83848以太网移植流程,可以TCP通信

DP83848-EP 是一款高度可靠、功能丰富的强大器件,包含了增强型 ESD 保护、MII 和 RMII,从而在 MPU 选择方面实现最大的灵活性,所有这些特性都融入于 48 引脚 PQFP 封装中。 DP83848-EP 配备 集成子层以支持 10BASE-T 和 100BASE-TX 以太网协议,这些协议确保了与基于其他标…

波动理论、传输线和S参数网络

波动理论、传输线和S参数网络 传输线 求解传输线方程 对于传输线模型&#xff0c;我们通常用 R L G C RLGC RLGC 来表示&#xff1a; 其中 R R R 可以表示导体损耗&#xff0c;由于电子流经非理想导体而产生的能量损耗。 G G G 表示介质损耗&#xff0c;由于非理想电介质…

基于pytorch的深度学习基础3——模型创建与nn.Module

三 模型创建与nn.Module 3.1 nn.Module 模型构建两要素&#xff1a; 构建子模块——__init()__拼接子模块——forward&#xff08;&#xff09; 一个module可以有多个module&#xff1b; 一个module相当于一个运算&#xff0c;都必须实现forward函数&#xff1b; 每一个mod…

Android--java实现手机亮度控制

文章目录 1、开发需求2、运行环境3、主要文件4、布局文件信息5、手机界面控制代码6、debug 1、开发需求 需求&#xff1a;开发一个Android apk实现手机亮度控制 2、运行环境 Android studio最新版本 3、主要文件 app\src\main\AndroidManifest.xml app\src\main\res\layou…

Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的

文章目录 一、前言二、主要内容三、小结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 在早期的计算机科学中&#xff0c;数组索引从 1 开始是很常见的。例如&#xff0c;Fortran 和 Pascal 等编程语言也采用了从 1 开始的索引。 这种索引…

【优选算法】复写零

链接&#xff1a;1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 算法原理&#xff1a; 解法&#xff1a;双指针算法 根据“异地”操作&#xff0c;然后优化成双指针下的“就地”操作 1.先找到最后一个“复写”的数 1.先判断 cur 位置的值 2.决定 dest 向后移动一步或…

鸿蒙之路的坑

1、系统 Windows 10 家庭版不可用模拟器 对应的解决方案【坑】 升级系统版本 直接更改密钥可自动升级系统 密钥找对应系统的&#xff08;例&#xff1a;windows 10专业版&#xff09; 升级完之后要激活 坑1、升级完后事先创建好的模拟器还是无法启动 解决&#xff1a;删除模拟…

大模型应用—IOPaint 图片去水印

IOPaint 是由 SOTA AI 模型提供支持的免费开源修复和修复工具,可以轻松实现图片去水印,去除图片不需要的部分,是目前效果最好的一个项目!完全免费开源 IOPaint 已经托管到 hugging face上,打开就可以直接免费使用,需要外网环境! 在线免费使用:【链接直达】 如果你需要…

SpringBoot项目的5种搭建方式(以idea2017为例)

目录 1. idea中使用官方API 2. idea中使用阿里云API 3. 在spring官网创建 4. 在阿里云官网创建 5. Maven项目改造成springboot项目 SpringBoot项目的创建细分一共有5种&#xff0c;其实主要分为以下三种&#xff1a; ①使用开发工具idea创建springboot项目&#xff08; Sp…

【Java 学习】详细讲解---包和导包、Scanner类、输入源

1. 包 1.1 什么是包&#xff1f; 举个例子&#xff0c;你和你的同学有不同的家庭&#xff0c;你们都有自己的爸爸妈妈&#xff0c;都有自己的家。在自己的家中你们可以按照自己爱好摆放东西&#xff0c;都互不干扰。但是&#xff0c;假如你们的家都在一起&#xff0c;你们就不…

某科技局国产服务器PVE虚拟化技术文档

环境介绍 硬件配置 服务器品牌&#xff1a;黄河 型号&#xff1a;Huanghe 2280 V2 Cpu型号&#xff1a;kunpeng-920 磁盘信息 :480SSD * 2 ,4T*4 网卡&#xff1a;板载四口千兆 如下表 四台服务器同等型号配置&#xff0c;均做单节点虚拟化&#xff0c;数据保护采用底层r…