怎样用C++实现备忘录模式 对象状态保存与恢复的实现

c++++中使用备忘录模式是为了在不破坏对象封装性的前提下实现状态的保存与恢复。1. 备忘录模式通过originator创建memento对象来保存内部状态,确保只有originator能访问和恢复该状态,从而保护封装性;2. caretaker负责存储和传递memento,但无法查看或修改其内容,实现了状态保存与业务逻辑的分离;3. 该模式避免了直接复制对象或暴露成员变量带来的高耦合和复杂性,尤其适用于处理包含私有成员、指针或引用的复杂对象状态;4. 使用时需注意内存消耗、深拷贝实现及性能开销,特别是频繁保存庞大状态可能导致的问题;5. 除备忘录模式外,还可根据需求选择命令模式(适合操作撤销/重做)、序列化(适合持久化存储)或原型模式(适合简单对象复制)。

怎样用C++实现备忘录模式 对象状态保存与恢复的实现

备忘录模式(Memento Pattern)在C++中,核心思想就是允许你在不破坏对象封装性的前提下,捕获并保存一个对象的内部状态,以便在需要时将对象恢复到这个状态。这就像给对象拍了一张快照,随时可以“回溯”到那个时间点。

怎样用C++实现备忘录模式 对象状态保存与恢复的实现

#include #include #include #include  // For std::shared_ptr// 1. Memento (备忘录) 类// 存储 Originator 的内部状态。通常,它只提供给 Originator 访问其状态的接口,// 而对 Caretaker 而言,它是一个不透明的对象。class Memento {private:    std::string state_; // 存储的具体状态数据    // 允许 Originator 访问 Memento 的私有状态    friend class Originator;    // 私有构造函数,确保只有 Originator 能创建 Memento    Memento(const std::string& state) : state_(state) {        // std::cout << "Memento created with state: " << state_ << std::endl;    }public:    // Caretaker 可以通过这个接口获取 Memento 的一些元信息,    // 但不能直接修改其内部状态    std::string GetName() const {        // 实际应用中可能返回时间戳或版本号        return "State_" + state_.substr(0, 5) + "...";    }    // 析构函数,如果内部有动态资源需要释放    ~Memento() {        // std::cout << "Memento for state '" << state_ << "' destroyed." << std::endl;    }};// 2. Originator (发起人) 类// 拥有一个需要被保存和恢复状态的对象。// 它负责创建 Memento 并使用 Memento 恢复其内部状态。class Originator {private:    std::string state_; // Originator 的当前状态public:    Originator(const std::string& state) : state_(state) {        std::cout << "Originator: Initializing with state: " << state_ << std::endl;    }    void DoSomething() {        // 模拟 Originator 进行一些操作,改变自身状态        static int counter = 0;        state_ = "State_" + std::to_string(counter++) + "_doing_something_important";        std::cout << "Originator: Changed state to: " << state_ << std::endl;    }    // 创建一个 Memento 来保存当前状态    std::shared_ptr Save() {        std::cout << "Originator: Saving current state to Memento." << std::endl;        return std::make_shared(state_);    }    // 从 Memento 恢复状态    void Restore(std::shared_ptr memento) {        if (memento) {            state_ = memento->state_; // 直接访问 Memento 的私有状态            std::cout << "Originator: Restoring state to: " << state_ << std::endl;        } else {            std::cout << "Originator: Cannot restore from a null Memento." << std::endl;        }    }    void ShowState() const {        std::cout << "Originator: Current state is: " << state_ << std::endl;    }};// 3. Caretaker (负责人) 类// 负责保存 Memento 对象,但从不检查或修改 Memento 的内容。// 它只知道 Memento 是一个可以被保存和检索的对象。class Caretaker {private:    std::vector<std::shared_ptr> mementos_; // 存储 Memento 对象的集合    std::shared_ptr originator_;         // 持有 Originator 的引用public:    Caretaker(std::shared_ptr originator) : originator_(originator) {}    void Backup() {        std::cout << "nCaretaker: Saving Originator's state..." <Save());    }    void Undo() {        if (mementos_.empty()) {            std::cout << "nCaretaker: No mementos to restore from." << std::endl;            return;        }        std::shared_ptr memento = mementos_.back();        mementos_.pop_back();        std::cout << "nCaretaker: Restoring state to: " <GetName() <Restore(memento);    }    void ShowHistory() const {        std::cout << "nCaretaker: Here's the list of mementos:" << std::endl;        for (const auto& memento : mementos_) {            std::cout << " - " <GetName() << std::endl;        }    }};// 客户端代码示例int main() {    auto originator = std::make_shared("Initial State");    auto caretaker = std::make_unique(originator);    caretaker->Backup(); // 保存初始状态    originator->DoSomething();    originator->ShowState();    caretaker->Backup(); // 保存新状态    originator->DoSomething();    originator->ShowState();    caretaker->Backup(); // 保存再一个新状态    originator->DoSomething();    originator->ShowState();    caretaker->ShowHistory();    std::cout << "n--- Performing Undo operations ---" <Undo(); // 恢复到上一个状态    originator->ShowState();    caretaker->Undo(); // 恢复到再上一个状态    originator->ShowState();    caretaker->Undo(); // 恢复到初始状态    originator->ShowState();    caretaker->Undo(); // 尝试恢复,但没有更多备忘录了    return 0;}

为什么在C++中要用备忘录模式?

我第一次接触到“状态保存与恢复”的需求时,脑子里最直接的想法就是直接复制对象,或者把对象的所有成员变量都暴露出来,然后手动赋值。但很快我就发现,这简直是个灾难。尤其是在C++这种强调封装的语言里,直接暴露内部状态不仅违反了面向对象的基本原则,更可能导致代码耦合度极高,难以维护。想象一下,如果一个对象的内部状态很复杂,包含各种私有成员、指针甚至其他对象的引用,你如何确保复制的完整性和正确性?手动处理深拷贝简直是噩梦。

怎样用C++实现备忘录模式 对象状态保存与恢复的实现

备忘录模式恰好解决了这个痛点。它巧妙地将“保存状态”的职责委托给对象自身(Originator),让它生成一个“备忘录”(Memento),这个备忘录对外部(Caretaker)来说是完全不透明的,你只能存储和传递它,而不能窥探或修改它的内容。只有Originator自己知道如何从备忘录中恢复状态。这就像你把一个加密的保险箱钥匙交给了Originator,只有它能打开并取出里面的东西,而Caretaker只是帮你保管这个保险箱。这种设计极大地保护了封装性,让状态的保存和恢复变得既安全又优雅。对我来说,它不仅仅是一种设计模式,更是一种对“责任分离”原则的深刻实践。

立即学习“C++免费学习笔记(深入)”;

备忘录模式的潜在挑战和注意事项是什么?

在我实际应用备忘录模式的过程中,也踩过一些坑,积累了一些经验。最常见的挑战之一就是备忘录的内存消耗。如果你的Originator对象状态非常庞大,或者你需要频繁地创建备忘录(比如实现一个深度很大的撤销/重做功能),那么存储大量的备忘录可能会迅速耗尽内存。我曾遇到过一个图形编辑器项目,每一步操作都生成一个完整的画布状态备忘录,结果几分钟操作下来,程序就因为内存溢出崩溃了。

怎样用C++实现备忘录模式 对象状态保存与恢复的实现

另一个棘手的问题是深拷贝与浅拷贝。如果Originator的状态包含指针或动态分配的资源,那么在创建备忘录时,你必须确保执行的是深拷贝,而不是简单地复制指针地址。否则,当Originator或备忘录被销毁时,可能会导致内存泄漏或双重释放的错误。这要求Originator在创建Memento时,对内部复杂数据结构的处理要格外小心,确保所有资源都被正确地复制到备忘录中。

最后,性能开销也是一个需要考虑的因素。创建和恢复备忘录可能涉及大量的内存分配、数据复制和销毁操作。在高性能要求的应用中,频繁地使用备忘录模式可能会引入不可接受的延迟。这时候,可能需要考虑优化策略,比如只保存状态的增量变化(Delta Memento),或者限制备忘录的数量。这让我头疼了一阵子,最终我们不得不权衡功能完整性和性能之间的平衡。

除了备忘录模式,还有哪些实现对象状态保存与恢复的方案?

有时候,我会问自己,是不是所有需要状态恢复的场景都非得用备忘录模式不可?答案显然不是。根据具体的需求和场景,我们还有其他一些选择,它们各有优缺点:

1. 命令模式(Command Pattern): 这是一个非常强大的模式,尤其适合实现撤销/重做功能。与备忘录模式关注“状态”不同,命令模式关注“操作”。每一个用户操作都被封装成一个命令对象,这个命令对象知道如何执行自己,也知道如何撤销自己。通过维护一个命令历史列表,就可以实现复杂的撤销和重做。我发现,在很多交互式应用中,命令模式比备忘录模式更灵活,因为它不仅能恢复状态,还能重放操作序列,但它的缺点是每个命令都需要实现撤销逻辑,这会增加代码量。

2. 序列化(Serialization): 如果你的主要目标是持久化对象状态,例如将对象保存到文件、数据库或通过网络传输,那么序列化是更直接的选择。C++本身没有内置的序列化机制,但有很多第三方库(如Boost.Serialization, Cereal, Protobuf等)可以实现。序列化将对象的状态转换为字节流,然后可以从字节流中重建对象。这与备忘录模式的短期内存管理不同,它更侧重于长期存储和跨进程/系统的数据交换。

3. 原型模式(Prototype Pattern): 当你需要创建大量相似对象,并且这些对象的创建成本很高时,原型模式很有用。它通过克隆现有对象来创建新对象。如果你的“状态保存”只是简单地复制一个对象的当前完整状态,并且这个对象没有复杂的内部结构(比如不包含裸指针),那么简单的深拷贝配合原型模式可能就足够了,而无需引入备忘录模式的复杂性。它避免了备忘录模式中 Originator 和 Memento 之间的耦合,但牺牲了对内部状态的封装性。

选择哪种方案,最终还是取决于你的具体需求:是需要精细的封装和回滚能力?是需要长期持久化?还是仅仅需要简单的对象复制?我通常会根据这些问题来做决策。

以上就是怎样用C++实现备忘录模式 对象状态保存与恢复的实现的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1472623.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月18日 19:46:47
下一篇 2025年12月18日 19:47:08

相关推荐

  • 怎样实现C++的安全内存访问 边界检查与智能指针结合方案

    c++++中实现安全内存访问需结合智能指针与边界检查。首先,使用std::unique_ptr或std::shared_ptr自动管理动态分配对象的生命周期,避免内存泄漏和悬空指针;其次,对数组或连续内存块,通过std::vector的at()方法或自定义封装类实现边界检查,防止越界访问;最后,结合…

    2025年12月18日 好文分享
    000
  • C++运行时类型识别 dynamic_cast typeid应用

    在C++中,运行时类型识别(RTTI, Run-Time Type Information)提供了在程序运行期间查询和操作对象类型的机制。其中,dynamic_cast 和 typeid 是RTTI的两个核心组成部分,主要用于处理继承体系中的类型转换与类型检查。 dynamic_cast:安全的向下…

    2025年12月18日
    000
  • 如何在Windows上搭建C++开发环境 Visual Studio安装配置指南

    答案:安装Visual Studio并选择“使用C++的桌面开发”工作负载即可快速搭建C++环境。下载Visual Studio Installer后,勾选该工作负载,完成安装后创建控制台应用项目,编写并运行Hello World程序验证环境。Visual Studio集成MSVC编译器、调试器和I…

    2025年12月18日
    000
  • C++智能指针 STL内存管理方案

    C++智能指针通过RAII机制自动管理内存,避免泄漏和悬空指针。std::unique_ptr独占所有权,高效安全;std::shared_ptr共享所有权,用引用计数管理生命周期;std::weak_ptr打破循环引用,实现非拥有式观察,三者结合STL容器可简化资源管理。 C++的智能指针,在我看…

    2025年12月18日
    000
  • C++标准库函数会抛出哪些异常 常见STL操作的异常行为说明

    c++++标准库中的函数和stl操作在出错时会抛出异常,常见的异常类型包括:1. std::logic_error(逻辑错误);2. std::runtime_error(运行时错误),如std::invalid_argument、std::out_of_range、std::length_erro…

    2025年12月18日 好文分享
    000
  • C++文件权限设置 chmod函数跨平台方案

    C++文件权限管理需跨平台考量,因Unix-like系统使用chmod函数基于“用户-组-其他”模型设置权限,而Windows采用基于ACL的复杂权限体系,仅能通过SetFileAttributes模拟部分属性,两者API与机制不兼容,故需条件编译实现适配。 在C++中处理文件权限,特别是要兼顾不同…

    2025年12月18日
    000
  • C++多线程优化 避免虚假共享方案

    虚假共享会导致多线程性能下降,因多线程修改同一缓存行中不同变量引发缓存频繁刷新;可通过alignas对齐或填充字段使变量独占缓存行,避免干扰;建议使用C++17的std::hardware_destructive_interference_size获取缓存行大小,并在高频写入场景中优先应用对齐优化,…

    2025年12月18日
    000
  • C++组合模式应用 树形结构处理方案

    组合模式通过统一接口处理树形结构,适用于文件系统等“部分-整体”场景,其核心由Component、Leaf和Composite构成,实现递归操作与统一调用。 在C++中处理树形结构时,组合模式(Composite Pattern)是一种非常自然且高效的设计模式选择。它允许你将对象组合成树形结构来表示…

    2025年12月18日
    000
  • C++算法异常处理 边界条件防御编程

    异常处理与边界检查是C++算法健壮性的核心,通过try-catch捕获非法输入如空容器,结合RAII管理资源,避免内存泄漏;在函数入口验证指针、下标、数值溢出等边界条件,辅以assert调试断言,确保程序稳定可靠。 在C++算法开发中,异常处理和边界条件的防御性编程是确保程序健壮性和稳定性的关键环节…

    2025年12月18日 好文分享
    000
  • C++实现图片转ASCII字符 像素灰度值转换技巧

    答案是将图像灰度值映射为ASCII字符,核心步骤包括:用stb_image加载图像,按gray=0.299×R+0.587×G+0.114×B计算灰度,选” .:-=+*#%@”等字符集,通过index=gray×(len-1)/255确定对应字符,下采样调整纵横比以适应终端…

    2025年12月18日
    000
  • C++配置文件处理 键值对解析与存储方案

    C++中通过文件处理配置而非硬编码,因文件方式具备高灵活性、职责分离和易维护性,支持多环境切换与非开发人员调整,避免重复编译部署;解析键值对时需处理空白字符、注释、重复键、分隔符冲突及编码问题,常用std::map或std::unordered_map存储,辅以trim、行解析和错误处理函数;对于复…

    2025年12月18日
    000
  • C++文件版本控制 简单版本管理实现

    答案:通过文件复制与元数据记录实现C++轻量级版本控制,使用时间戳命名版本文件并配合日志记录变更内容,结合命令行工具或IDE集成实现自动化保存与恢复,避免手动备份混乱、存储膨胀等问题,适用于个人或小型项目。 C++文件版本控制,尤其是在我们不想或者没必要引入Git这样大型工具的时候,其核心在于建立一…

    2025年12月18日
    000
  • C++内存泄漏是什么 常见泄漏场景与检测方法

    C++内存泄漏因未释放动态分配内存导致程序性能下降或崩溃,常见于new/delete不匹配、异常退出、指针重赋值等场景;可通过智能指针、RAII、Valgrind、AddressSanitizer等工具检测与预防,建议使用现代C++特性减少手动管理。 C++内存泄漏是指程序在动态分配内存后,未能正确…

    2025年12月18日
    000
  • C++文件流缓冲区如何手动刷新 flush与endl的区别与使用场景

    缓冲区刷新是指将内存中缓冲区的数据强制写入磁盘文件的过程。c++++文件流操作中,数据先写入内存缓冲区,并非立即写入文件,只有在缓冲区满、文件流关闭或程序正常退出时才会自动刷新;但为确保关键数据及时写入,需手动刷新。1. flush:只刷新缓冲区,不添加换行符,适用于需要即时写入但不希望换行的场景,…

    2025年12月18日 好文分享
    000
  • C++高性能计算环境怎么搭建 OpenMP和MPI配置

    搭建C++高性能计算环境需配置编译器、OpenMP、MPI和构建系统。1. 选GCC或Clang等支持OpenMP的编译器,Linux下通过包管理器安装,Windows推荐使用WSL;2. OpenMP通过-fopenmp启用,适用于单节点多核共享内存并行;3. 安装Open MPI或MPICH实现…

    2025年12月18日
    000
  • C++原子操作怎么用 memory_order内存序详解

    答案:C++内存序控制原子操作的内存访问顺序,六种内存序分为顺序一致性、获取-释放语义和松散内存序三类,合理选择可提升性能;默认seq_cst最安全但慢,acquire/release用于线程同步,relaxed仅保证原子性适用于计数器;使用时应先保证正确性再优化性能。 在C++多线程编程中,原子操…

    2025年12月18日
    000
  • 联合体是什么概念 union关键字基本用法解析

    联合体(union)是一种内存共享的数据结构,所有成员共用同一块内存空间,大小由最大成员决定,同一时间只能使用一个成员。与结构体不同,结构体为每个成员分配独立内存,可同时访问所有成员。联合体常用于内存优化、类型双关和变体类型表示,但需手动管理活跃成员,避免未定义行为、字节序问题及类型别名规则冲突。C…

    2025年12月18日
    000
  • 运算符重载如何实现 算术运算符重载示例

    运算符重载允许自定义类型使用标准运算符,提升代码可读性;在C++中,可通过成员或友元函数重载算术运算符,如Complex类重载+、-、*、/等,实现复数运算,返回新对象且不修改原对象,复合赋值运算符如+=则修改自身并返回引用。 在面向对象编程中,运算符重载允许我们为自定义类型(如类或结构体)赋予标准…

    2025年12月18日
    000
  • C++中类的前向声明有什么用 降低编译时间依赖的技巧

    前向声明通过仅声明类名而非完整定义来解决循环依赖并减少编译时间。1. 它允许类a使用类b的指针或引用而无需立即知道其完整定义;2. 只能在头文件中声明类名,且只能用于指针或引用;3. 若需创建对象或访问成员,仍需包含完整头文件;4. 减少不必要的编译依赖,提升大型项目编译效率;5. 不应过度使用以避…

    2025年12月18日 好文分享
    000
  • C++联合体类型安全 数据解释注意事项

    安全使用C++联合体需结合枚举跟踪数据类型,如定义DataType枚举与联合体Data配合使用,通过type字段判断当前有效成员,避免跨类型误读;示例中Variant结构体实现类型安全访问,先写入整型再读取字符串时依赖type判断输出正确结果;此外可采用C++17的std::variant替代传统联…

    2025年12月18日
    000

发表回复

登录后才能评论
关注微信