0

0

C++备忘录模式如何实现对象状态保存 序列化与恢复机制

P粉602998670

P粉602998670

发布时间:2025-08-06 11:04:01

|

808人浏览过

|

来源于php中文网

原创

备忘录模式是一种行为型设计模式,其核心在于在不破坏封装性的前提下捕获并外部化对象内部状态,以便之后可恢复该状态。1. 它包含三个核心角色:发起人(originator)负责创建和恢复状态;备忘录(memento)存储状态且对外隐藏实现细节;管理者(caretaker)保存备忘录但不查看其内容。2. 在c++中实现该模式通常需要手动或借助第三方库完成序列化与反序列化操作,如boost.serialization、protocol buffers等。3. 该模式适用于撤销/重做功能、事务管理、游戏开发、配置管理和有限状态机等场景。4. 在多线程环境中使用时,应通过互斥锁、深拷贝、不可变对象等方式确保线程安全。5. 备忘录模式与命令模式的区别在于前者用于状态回退,后者用于操作解耦,二者也可结合使用以实现更复杂的撤销机制。

C++备忘录模式如何实现对象状态保存 序列化与恢复机制

C++备忘录模式的核心在于提供一种在不破坏封装性的前提下,捕获并外部化对象内部状态的方法,以便在之后可以将对象恢复到之前的状态。这涉及到状态的保存(序列化)和状态的恢复(反序列化),是一种非常实用的设计模式。

C++备忘录模式如何实现对象状态保存 序列化与恢复机制

解决方案

C++备忘录模式如何实现对象状态保存 序列化与恢复机制

备忘录模式通常包含三个角色:发起人(Originator)、备忘录(Memento)和管理者(Caretaker)。

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

  1. 发起人(Originator): 拥有需要保存状态的内部数据,并负责创建备忘录和从备忘录恢复自身状态。
  2. 备忘录(Memento): 存储发起人的内部状态,对外隐藏具体实现细节。
  3. 管理者(Caretaker): 负责保存备忘录对象,但不检查备忘录的内容。

下面是一个简单的C++示例,展示了如何实现备忘录模式:

C++备忘录模式如何实现对象状态保存 序列化与恢复机制
#include 
#include 
#include 

// 备忘录类
class Memento {
public:
    Memento(const std::string& state) : state_(state) {}
    std::string getState() const { return state_; }
private:
    std::string state_;
};

// 发起人类
class Originator {
public:
    void setState(const std::string& state) {
        state_ = state;
        std::cout << "State set to: " << state_ << std::endl;
    }

    Memento saveStateToMemento() {
        std::cout << "Saving state to Memento." << std::endl;
        return Memento(state_);
    }

    void getStateFromMemento(const Memento& memento) {
        state_ = memento.getState();
        std::cout << "State restored from Memento: " << state_ << std::endl;
    }

private:
    std::string state_;
};

// 管理者类
class Caretaker {
public:
    void add(const Memento& state) {
        mementos_.push_back(state);
    }

    Memento get(int index) const {
        return mementos_[index];
    }

private:
    std::vector mementos_;
};

int main() {
    Originator originator;
    Caretaker caretaker;

    originator.setState("State #1");
    caretaker.add(originator.saveStateToMemento());

    originator.setState("State #2");
    caretaker.add(originator.saveStateToMemento());

    originator.setState("State #3");
    std::cout << "Current State: " << std::endl;

    originator.getStateFromMemento(caretaker.get(0));
    std::cout << "First saved State: " << std::endl;
    originator.getStateFromMemento(caretaker.get(1));
    std::cout << "Second saved State: " << std::endl;

    return 0;
}

这个例子简单展示了备忘录模式的运作方式。 状态的保存和恢复,在这里是直接通过字符串进行的,但实际应用中,可能需要更复杂的序列化机制。

C++中如何实现对象的序列化与反序列化?

在C++中,序列化是将对象的状态转换为可以存储或传输的格式(例如,字节流),而反序列化则是将这种格式转换回对象的过程。 C++本身没有内置的序列化机制,所以通常需要借助第三方库或者手动实现。

  1. 手动实现: 对于简单的类,可以手动编写序列化和反序列化的代码。 这通常涉及到将对象的每个成员变量写入到输出流,并在反序列化时从输入流读取这些变量。

    #include 
    #include 
    #include 
    
    class MyObject {
    public:
        MyObject() : x(0), y(0), message("") {}
        MyObject(int x, int y, const std::string& message) : x(x), y(y), message(message) {}
    
        void serialize(std::ofstream& ofs) const {
            ofs.write(reinterpret_cast(&x), sizeof(x));
            ofs.write(reinterpret_cast(&y), sizeof(y));
            size_t len = message.size();
            ofs.write(reinterpret_cast(&len), sizeof(len));
            ofs.write(message.c_str(), len);
        }
    
        void deserialize(std::ifstream& ifs) {
            ifs.read(reinterpret_cast(&x), sizeof(x));
            ifs.read(reinterpret_cast(&y), sizeof(y));
            size_t len;
            ifs.read(reinterpret_cast(&len), sizeof(len));
            message.resize(len);
            ifs.read(&message[0], len);
        }
    
        void print() const {
            std::cout << "x: " << x << ", y: " << y << ", message: " << message << std::endl;
        }
    
    private:
        int x;
        int y;
        std::string message;
    };
    
    int main() {
        MyObject obj1(10, 20, "Hello, Serialization!");
        std::ofstream ofs("object.dat", std::ios::binary);
        obj1.serialize(ofs);
        ofs.close();
    
        MyObject obj2;
        std::ifstream ifs("object.dat", std::ios::binary);
        obj2.deserialize(ifs);
        ifs.close();
    
        obj2.print(); // 输出: x: 10, y: 20, message: Hello, Serialization!
    
        return 0;
    }
  2. 使用第三方库: 存在许多C++序列化库,例如Boost.Serialization、Google Protocol Buffers、 cereal等。 这些库提供了更强大、更灵活的序列化功能,可以处理复杂的对象结构。

    • Boost.Serialization: Boost.Serialization是一个非常流行的库,它支持多种序列化格式(例如,二进制、XML)。 它需要一些配置,但功能非常强大。

      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      
      class MyObject {
      public:
          MyObject() : x(0), y(0), message("") {}
          MyObject(int x, int y, const std::string& message) : x(x), y(y), message(message) {}
      
          void print() const {
              std::cout << "x: " << x << ", y: " << y << ", message: " << message << std::endl;
          }
      
      private:
          int x;
          int y;
          std::string message;
      
          friend class boost::serialization::access;
          template
          void serialize(Archive & ar, const unsigned int version)
          {
              ar & x;
              ar & y;
              ar & message;
          }
      };
      
      int main() {
          MyObject obj1(10, 20, "Hello, Boost Serialization!");
      
          std::ofstream ofs("object.dat");
          boost::archive::text_oarchive oa(ofs);
          oa << obj1;
          ofs.close();
      
          MyObject obj2;
          std::ifstream ifs("object.dat");
          boost::archive::text_iarchive ia(ifs);
          ia >> obj2;
          ifs.close();
      
          obj2.print(); // 输出: x: 10, y: 20, message: Hello, Boost Serialization!
      
          return 0;
      }
    • Google Protocol Buffers: Protocol Buffers是Google开发的一种语言无关、平台无关、可扩展的序列化结构数据的方法。 它特别适合于数据存储和通信协议。

  3. 选择合适的序列化方法: 选择哪种序列化方法取决于你的需求。 如果只需要处理简单的类,手动实现可能就足够了。 如果需要处理复杂的对象结构,或者需要与其他语言或平台进行互操作,那么使用第三方库可能更合适。

备忘录模式在实际开发中的应用场景有哪些?

备忘录模式在需要保存和恢复对象状态的场景中非常有用。

  1. 撤销/重做功能: 在文本编辑器、图像处理软件等应用中,备忘录模式可以用来实现撤销和重做功能。 每次执行一个操作时,都创建一个备忘录来保存当前状态,以便在需要时可以恢复到之前的状态。

  2. 事务管理: 在数据库系统中,备忘录模式可以用来实现事务的回滚。 在执行一个事务之前,创建一个备忘录来保存数据库的原始状态,如果在事务执行过程中发生错误,可以恢复到原始状态。

  3. 游戏开发: 在游戏中,备忘录模式可以用来保存游戏的状态,例如玩家的位置、生命值、物品等。 这样,玩家可以在需要时加载之前的游戏状态。 例如,保存游戏的进度,或者实现“死亡回放”功能。

    Type Studio
    Type Studio

    一个视频编辑器,提供自动转录、自动生成字幕、视频翻译等功能

    下载
  4. 配置管理: 在应用程序中,备忘录模式可以用来保存配置信息。 用户可以修改配置,并在需要时恢复到之前的配置。

  5. 有限状态机: 在有限状态机中,备忘录模式可以用来保存状态机的状态。 这样,状态机可以在需要时恢复到之前的状态。

如何在多线程环境中使用备忘录模式?

在多线程环境中使用备忘录模式需要特别小心,以避免数据竞争和死锁等问题。

  1. 线程安全: 备忘录对象本身应该是线程安全的。 这意味着,多个线程可以同时访问备忘录对象,而不会导致数据损坏。 一种常见的做法是使用互斥锁(mutex)来保护备忘录对象的内部状态。

  2. 发起人的状态复制: 在创建备忘录时,应该对发起人的状态进行深拷贝,而不是浅拷贝。 这样,即使发起人的状态在创建备忘录之后发生了改变,备忘录对象仍然保存的是原始状态。

  3. 避免长期持有锁 在创建和恢复备忘录时,应该尽量避免长时间持有锁。 这可以通过将操作分解为更小的步骤,并在每个步骤之后释放锁来实现。

  4. 使用原子操作: 对于简单的状态变量,可以使用原子操作来保证线程安全,而无需使用互斥锁。

  5. 考虑使用不可变对象: 如果可能,可以将备忘录对象设计为不可变对象。 这意味着,备忘录对象一旦创建,就不能被修改。 这样可以避免数据竞争问题。

下面是一个简单的示例,展示了如何在多线程环境中使用备忘录模式:

#include 
#include 
#include 
#include 

class Memento {
public:
    Memento(const std::string& state) : state_(state) {}
    std::string getState() const { return state_; }
private:
    std::string state_;
};

class Originator {
public:
    void setState(const std::string& state) {
        std::lock_guard lock(mutex_);
        state_ = state;
        std::cout << "State set to: " << state_ << " by thread " << std::this_thread::get_id() << std::endl;
    }

    Memento saveStateToMemento() {
        std::lock_guard lock(mutex_);
        std::cout << "Saving state to Memento by thread " << std::this_thread::get_id() << std::endl;
        return Memento(state_);
    }

    void getStateFromMemento(const Memento& memento) {
        std::lock_guard lock(mutex_);
        state_ = memento.getState();
        std::cout << "State restored from Memento: " << state_ << " by thread " << std::this_thread::get_id() << std::endl;
    }

private:
    std::string state_;
    std::mutex mutex_;
};

class Caretaker {
public:
    void add(const Memento& state) {
        std::lock_guard lock(mutex_);
        mementos_.push_back(state);
    }

    Memento get(int index) const {
        std::lock_guard lock(mutex_);
        return mementos_[index];
    }

private:
    std::vector mementos_;
    mutable std::mutex mutex_;
};

int main() {
    Originator originator;
    Caretaker caretaker;

    std::thread t1([&]() {
        originator.setState("State #1 from thread 1");
        caretaker.add(originator.saveStateToMemento());
    });

    std::thread t2([&]() {
        originator.setState("State #2 from thread 2");
        caretaker.add(originator.saveStateToMemento());
    });

    t1.join();
    t2.join();

    std::thread t3([&]() {
        originator.getStateFromMemento(caretaker.get(0));
        std::cout << "First saved State from thread 3: " << std::endl;
    });

    std::thread t4([&]() {
        originator.getStateFromMemento(caretaker.get(1));
        std::cout << "Second saved State from thread 4: " << std::endl;
    });

    t3.join();
    t4.join();

    return 0;
}

这个例子使用互斥锁来保护发起人和管理者的内部状态,以避免数据竞争。 需要注意的是,这只是一个简单的示例,实际应用中可能需要更复杂的线程安全机制。

备忘录模式与命令模式的区别是什么?

备忘录模式和命令模式都是行为型设计模式,但它们解决的问题不同。

  • 备忘录模式: 关注的是对象状态的保存和恢复。 它允许在不破坏封装性的前提下,捕获并外部化对象内部状态,以便在之后可以将对象恢复到之前的状态。

  • 命令模式: 关注的是将请求封装成对象,以便可以使用不同的请求、队列请求或日志请求来参数化其他对象。 命令模式允许将操作的调用者与操作的执行者解耦。

简单来说,备忘录模式是用来“回退”状态的,而命令模式是用来“执行”操作的。 它们之间的主要区别在于:

  • 目的: 备忘录模式的目的是保存和恢复对象的状态,而命令模式的目的是将请求封装成对象。
  • 角色: 备忘录模式包含发起人、备忘录和管理者三个角色,而命令模式包含命令、接收者和调用者三个角色。
  • 行为: 备忘录模式的行为是保存和恢复状态,而命令模式的行为是执行操作。

虽然它们的目的不同,但有时也可以将它们结合起来使用。 例如,可以使用命令模式来封装对对象状态的修改操作,并使用备忘录模式来保存每次修改之前的状态,以便可以撤销这些操作。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
pdf怎么转换成xml格式
pdf怎么转换成xml格式

将 pdf 转换为 xml 的方法:1. 使用在线转换器;2. 使用桌面软件(如 adobe acrobat、itext);3. 使用命令行工具(如 pdftoxml)。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1897

2024.04.01

xml怎么变成word
xml怎么变成word

步骤:1. 导入 xml 文件;2. 选择 xml 结构;3. 映射 xml 元素到 word 元素;4. 生成 word 文档。提示:确保 xml 文件结构良好,并预览 word 文档以验证转换是否成功。想了解更多xml的相关内容,可以阅读本专题下面的文章。

2091

2024.08.01

xml是什么格式的文件
xml是什么格式的文件

xml是一种纯文本格式的文件。xml指的是可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。想了解更多相关的内容,可阅读本专题下面的相关文章。

1051

2024.11.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1498

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

623

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

592

2024.03.22

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

31

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 7.7万人学习

C 教程
C 教程

共75课时 | 4.2万人学习

C++教程
C++教程

共115课时 | 14万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号