前言
nav2
系列教材,yolov11
部署,系统迁移教程我会放到年后一起更新,最近年末手头事情多,还请大家多多谅解。
- 上一节我们讲述了C++
移动语义
相关的知识,本期我们来看看C++中常用的几种智能指针,并看看他们在设计模式
中的运用。- 【C++移动语义与完美转发】左值右值,引用,引用折叠,移动语义,万能引用与完美转发
1 RAII
- RAII(Resource Acquisition Is Initialization)是一种广泛应用于 C++ 中的编程范式,特别是在资源管理和内存管理方面。RAII 的核心思想是资源的获取和释放与对象的生命周期绑定。资源的获取(如内存、文件句柄、网络连接等)通过构造函数完成,而资源的释放则通过析构函数完成。
1-1 RAII 的基本概念
- 在 RAII 模式中,资源管理由对象的生命周期来控制。对象在创建时获取资源,在销毁时释放资源。这种方式有两个主要的好处:
- 自动释放资源:对象的析构函数会自动释放资源,确保不会忘记释放。
- 异常安全:由于析构函数会在对象生命周期结束时自动调用,它确保了即使发生异常,资源也能正确释放。
1-2 RAII 的工作原理
- RAII 依赖于对象的构造和析构过程来管理资源,具体工作流程如下:
- 资源获取(Acquisition):
- 在对象的构造函数中,获取资源。比如,打开一个文件、分配内存、获取数据库连接等。
- 资源释放(Release):
- 在对象的析构函数中,释放资源。这样,当对象生命周期结束时,资源会自动被释放。
- 资源获取(Acquisition):
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 中,确实引入了一些智能指针类型,来帮助管理对象的生命周期,减少内存泄漏和悬挂指针等问题。
std::unique_ptr
— 引入于 C++11,提供唯一所有权语义。std::shared_ptr
— 引入于 C++11,提供共享所有权语义。std::weak_ptr
— 引入于 C++11,用于解决std::shared_ptr
之间的循环引用问题。std::auto_ptr
— 在 C++98 中引入,在 C++11 中被废弃,并在 C++17 中移除。(本文不讲)
2-2 智能指针前置知识decltype
- 这一小节我们会补充一些
C++
冷知识 decltype
是 C++11 引入的关键字,用于获取一个表达式的类型,或者说是推导某个对象、变量、表达式的类型。它非常强大,可以在编译时自动推导出类型,常用于模板编程或者自动推导复杂类型的场景。decltype
的基本语法:expression
:可以是任何表达式,比如变量、函数调用、类型、运算结果等。
decltype(expression)
decltype
的作用
- 推导类型:
decltype
根据一个给定的表达式来推导出其类型。
int a=10;
decltype(a) b=a;
- 与
auto
配合使用:decltype
可以与auto
搭配使用,用于推导复杂表达式的类型。
auto x = 10; // auto 推导出 x 的类型是 int
decltype(x) y = 20; // y 的类型也是 int
- 获取函数返回类型:
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 特性分析
- 引用计数与析构:
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 仍然拥有对象
- 访问管理的对象:使用 get() 方法可以获取裸指针,并且可以使用 * 和 -> 操作符来访问所管理的对象:
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
MyClass* raw_ptr = ptr.get(); // 获取裸指针
ptr->doSomething(); // 使用 -> 访问成员
(*ptr).doSomething(); // 使用 * 访问成员
- 自定义删除器:
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 特性
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;
}
use_count()
:use_count()
返回当前有多少个 std::shared_ptr 管理该对象的引用。注意,std::weak_ptr 不会增加引用计数。
std::cout << "use_count() == " << wp.use_count() << std::endl; // 输出引用计数
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;
}
- 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;
}
-
上述修改后的代码,
A
和B
之间通过std::shared_ptr
互相持有对方。为了避免循环引用,我们将A
中的B
指针改为std::weak_ptr<B>
。这样,A
对B
的引用不会增加B
的引用计数,从而打破了循环引用。当作用域结束时,A
和B
都会被正确销毁。
5-4 std::weak_ptr
和 std::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_ptr
或std::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_ptr
或std::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 使得在发生异常时,资源能够被正确地释放,从而保证程序的稳定性。
- 如有错误,欢迎指出!
- 感谢大家的支持