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

news2024/12/22 6:32:03

前言

  • 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";  // 20

    auto& elementRef = getElementAtIndexRef(vec, 1);  // 返回的是引用类型
    elementRef = 100; 
    std::cout << "vec[1]: " << vec[1] << "\n";  // 100

    return 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_ptr
    std::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 持有 B
    b->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_ptr

if (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 持有 B
    b->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_ptr

void observe() {
    std::cout << "gw.use_count() == " << gw.use_count() << "; ";
    if (auto spt = gw.lock()) {  // 使用 lock() 获得 shared_ptr
        std::cout << "*spt == " << *spt << '\n';
    } else {
        std::cout << "gw is expired\n";
    }
}

int main() {
    {
        auto sp = std::make_shared<int>(42);  // 创建 shared_ptr
        gw = sp;  // 将 shared_ptr 转换为 weak_ptr

        observe();  // 调用 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;

    // 使用策略A
    context.setStrategy(std::make_shared<ConcreteStrategyA>());
    context.executeStrategy();

    // 使用策略B
    context.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.coloradmin.cn/o/2263624.html

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

相关文章

基于单片机的病房呼叫系统设计

摘 要&#xff1a; 文章基于 51 系列的单片机设计的病房呼叫系统 。 在以 AT89C51 单片机为核心&#xff0c;以 74HC573 锁存器 、数码管显示模块、 矩阵按键模块等为辅组成的&#xff0c;按键分布在各个病床的床头&#xff0c;可以节约接口资源&#xff0c;当按下按键&a…

编译原理复习---目标代码生成

适用于电子科技大学编译原理期末考试复习。 1. 目标代码 是目标机器的汇编代码或机器码&#xff0c;在本课程中指的是类似于汇编代码的一种形式&#xff0c;由一条条的指令构成目标代码。 抽象机指令格式&#xff1a;OP 目的操作数&#xff0c;源操作数。 我们要做的&…

Redis数据对象

基本结构图 key和value指向的是redisObject对象 type&#xff1a;标识该对象用的是什么类型&#xff08;String、List Redis数据结构 SDS SDS有4个属性&#xff1a; len&#xff1a;记录了字符串长度&#xff0c;因此获取字符串长度的时候时间复杂度O&#xff08;1&#xff…

Gale-Shapley算法

一. 设计目的 盖尔-沙普利算法&#xff08;Gale-Shapley算法&#xff09;的设计目的是为了解决稳定匹配问题&#xff0c;即在给定一组男性和女性的偏好列表的情况下&#xff0c;找到一个稳定的匹配。这里的“稳定”指的是不存在任何一对男性和女性&#xff0c;他们彼此都比当前…

JWT令牌与微服务

1. 什么是JWT JWT&#xff08;JSON Web Token&#xff09;是一种开放标准(RFC 7519)&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于作为JSON对象在各方之间安全地传输信息。JWT通常用于身份验证和信息交换。 以下是JWT的一些关键特性&#xff1a; 紧凑&#xff…

视频点播系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…

docker部署Melody开源音乐管理工具

Melody是一款非常实用的开源音乐管理工具。它不仅功能强大、操作简便&#xff0c;还支持多平台检索和一键下载/上传功能。更重要的是&#xff0c;它还支持一键“解锁”无法播放的歌曲和多端适配。如果你也是音乐爱好者&#xff0c;不妨试试Melody&#xff0c;让你的音乐生活更加…

问题小记-达梦数据库报错“字符串转换出错”处理

最近遇到一个达梦数据库报错“-6111: 字符串转换出错”的问题&#xff0c;这个问题主要是涉及到一条sql语句的执行&#xff0c;在此分享下这个报错的处理过程。 问题表现为&#xff1a;一样的表结构和数据&#xff0c;执行相同的SQL&#xff0c;在Oracle数据库中执行正常&…

day4:tomcat—maven-jdk

一&#xff0c;java项目部署过程 编译&#xff1a;使用javac命令将.java源文件编译成.class宇节码文件打包&#xff1a;使用工具如maven或Gradle将项目的依赖、资源和编译后的字节码打包成一个分发格式&#xff0c;如.jar文件&#xff0c;或者.war文件(用于web应用&#xff09…

【D3.js in Action 3 精译_046】DIY 实战:在 Observable 平台利用饼图布局函数实现 D3 多个环形图的绘制

当前内容所在位置&#xff1a; 第五章 饼图布局与堆叠布局 ✔️ 5.1 饼图和环形图的创建 ✔️ 5.1.1 准备阶段&#xff08;一&#xff09;5.1.2 饼图布局生成器&#xff08;二&#xff09;5.1.3 圆弧的绘制&#xff08;三&#xff09;5.1.4 数据标签的添加&#xff08;四&#…

Swin transformer 论文阅读记录 代码分析

该篇文章&#xff0c;是我解析 Swin transformer 论文原理&#xff08;结合pytorch版本代码&#xff09;所记&#xff0c;图片来源于源paper或其他相应博客。 代码也非原始代码&#xff0c;而是从代码里摘出来的片段&#xff0c;配上简单数据&#xff0c;以便理解。 当然&…

Vscode搭建C语言多文件开发环境

一、文章内容简介 本文介绍了 “Vscode搭建C语言多文件开发环境”需要用到的软件&#xff0c;以及vscode必备插件&#xff0c;最后多文件编译时tasks.json文件和launch.json文件的配置。即目录顺序。由于内容较多&#xff0c;建议大家在阅读时使用电脑阅读&#xff0c;按照目录…

麒麟操作系统服务架构保姆级教程(二)sersync、lsync备份和NFS持久化存储

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 上篇文章我们说到rsync虽好&#xff0c;但是缺乏实时性&#xff0c;在实际应用中&#xff0c;咱们可以将rsync写进脚本&#xff0c;然后写进定时任务去备份&#xff0c;如果每天凌晨1&#xff1a;00…

关于小程序内嵌h5打开新的小程序

关于小程序内嵌h5打开新的小程序 三种方式 https://juejin.cn/post/7055551463489011749 只依赖于h5本身的就是 https://huaweicloud.csdn.net/64f97ebb6b896f66024ca16c.html https://juejin.cn/post/7055551463489011749 navigateToMiniProgram 故小程序webview里的h5无法…

QP:Query切词

Query 分词&#xff08;切词&#xff09; 分词指将一段连续的文本切成一个个独立且有意义的词汇&#xff0c;在文本召回中会对 Doc 文本内容分词以构建索引&#xff0c;并通过对查询词 Query 分词后去做检索。Query 分词在搜索中是一个基础信号&#xff0c;除了文本召回&#…

鸿蒙元服务从0到上架【第二篇】

第一招&#xff1a;在AppGallery后台下载对应的证书等文件 AppGallery后台 新增发布证书&#xff0c;具体操作可查看申请发布证书 申请发布Profile证书 第二招&#xff1a;在IDE中填写 第三招&#xff1a;打包【⚠️发布上架的只能是Build App】 终端展示这一片绿&#xf…

9_HTML5 SVG (5) --[HTML5 API 学习之旅]

SVG 模糊效果 HTML5中的SVG&#xff08;可缩放矢量图形&#xff09;允许我们创建高质量的二维图形&#xff0c;包括应用各种滤镜效果。模糊效果是通过<feGaussianBlur>滤镜原语来实现的。下面我将给出4个使用SVG进行模糊效果处理的示例&#xff0c;并为每个代码段添加详…

vue+node+mysql8.0,详细步骤及报错解决方案

1.下载需要安装的插件 下载express npm install express下载cors&#xff0c;用于处理接口跨域问题 npm install cors下载mysql npm install mysql 2.配置服务器 可以在vue项目的src同级创建server文件夹&#xff08;这里的位置可随意选择&#xff09; 然后依次创建&#…

相机雷达外参标定综述“Automatic targetless LiDAR–camera calibration: a survey“

相机雷达外参标定综述--Automatic targetless LiDAR–camera calibration: a survey 前言1 Introduction2 Background3 Automatic targetless LiDAR–camera calibration3.1 Information theory based method(信息论方法)3.1.1 Pairs of point cloud and image attributes(属性…

Epic游戏使用mod

以土豆兄弟为例&#xff1a; 第一步&#xff1a;获取 SteamCMD 下载官方 Steam 控制台客户端 (steamCMD) 1. 下载好后打开&#xff0c;是一个在 cmd 窗口的运行的命令行 2. 输入以下代码登录 login anonymous 第二步&#xff1a; 确认自己要下载的游戏 ID 和 mod ID 然后…