【设计模式精讲】23.备忘录模式(Memento)

【设计模式精讲】23.备忘录模式(Memento)

【摘要】:命令模式靠「逆操作」撤销了绘图程序,但文本编辑器是另一回事------用户随手打字删字、移动光标,操作琐碎得没有形状。两条歪路摆在面前:为存档把字段全 public,封装陪葬;或者整个对象拷一份,句柄崩、「哪些字段属于状态」没人说得清。本文给出备忘录模式的 GoF 意图:在不破坏封装的前提下捕获并外化对象内部状态------C++ 用嵌套类加 friend 精确表达「只有本体能拆信、看守者只管邮递」的宽窄接口;现代版补上移动语义、快照与命令的成本曲线(大型系统的混合方案),以及值语义快照的取舍。文末对照 std::exception_ptr、folly 的 exception_wrapper 与 AOSP 的状态保存协议。读完你能为「回到过去」选对那条路线。

【关键词】:备忘录、快照、撤销、封装、看守者、宽窄接口、事务回滚

【代码基准】:C++17

1. 撤销的另一种姿势:把「当时」存下来

第 19 篇用命令模式解了绘图程序的撤销:每个操作规整、可逆,命令对象自带 undo。现在换一个主角------文本编辑器的 Document:用户打一个字、删一个字、光标跳来跳去,操作琐碎到没有形状,「为每个字符输入写一个命令类」荒谬得说不出口。需求还是那个 Ctrl+Z,直觉给出两条路:

cpp 复制代码
// 说明性片段
// ❌ 路 A:为存档打开封装
class Document {
 public:
  std::string text;   // 全部 public
  int cursor;         // 谁都能改,
  Selection sel;      // 不变量没人守
};
// 历史栈直接搬字段:
// hist.push_back({doc.text, doc.cursor});
// ------ 漏存了选区,恢复后选区悬垂

// ❌ 路 B:整个对象拷一份
// hist2.push_back(doc);   // 要求可拷贝;
// 内部若有缓存/句柄/自指指针,
// 拷出来的就是一颗雷

路 A 让「不变量由类自己守」的封装原则为撤销功能陪葬------第 2 篇讲过的边界一旦打开就关不上;路 B 把「哪些字段属于状态」的定义权交给了拷贝构造------多拷浪费内存,少拷恢复不完整,含资源的字段(打开的文件、注册的回调)根本拷不动。

两条路背后是同一个矛盾命题:「恢复过去」要求外部能拿到内部状态,「封装」要求外部拿不到 。备忘录模式的全部智慧就是解开这一对矛盾------答案不是二选一,而是把「状态」打包成一件外部能保管、但不能拆开的东西。

2. 模式意图与定义

  • 一句话定义 :在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,从而可以将该对象恢复到原先保存的状态。
    解决的问题:需要快照式回滚(撤销、事务、存档、回溯),而对象的内部表示不能对外暴露。
  • GoF 原文意图Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. 注意打头的 Without violating encapsulation------这不是附带的好处,是模式的立身之本,也是它与「字段全 public 加一个 copy」的分界线。
  • Refactoring Guru 的表述:备忘录模式允许在不暴露所保存状态实现细节的前提下,保存与恢复对象先前的状态;它像是对象的「时刻存档」,外界可以传递存档、保管存档,但看不懂存档。

三条定性:

  1. 保管权与解释权分离 。看守者(Caretaker)只做三件事------存、传、丢;只有原发器(Originator)能写快照、读快照。快照对外界是不透明的黑盒,「不透明」正是封装没有破裂的证明。
  2. 宽接口与窄接口 。GoF 用两个视图描述同一份快照:原发器看到宽接口(能读写全部状态),看守者看到窄接口(只有「这是一份快照」的存在性)。C++ 有一把恰好合手的刀------friend:把宽接口精确地只授予原发器,第 4 节落地。
  3. 与命令模式的分工。第 19 篇记「做了什么」(操作 + 逆操作),本篇记「当时什么样」(状态快照)------这是撤销的两条路线,成本曲线完全不同,第 5 节正面比较。

3. UML 图 + 结构说明

#mermaid-svg-cSN6hYuFnBXE4P4Y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cSN6hYuFnBXE4P4Y .error-icon{fill:#552222;}#mermaid-svg-cSN6hYuFnBXE4P4Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cSN6hYuFnBXE4P4Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cSN6hYuFnBXE4P4Y .marker.cross{stroke:#333333;}#mermaid-svg-cSN6hYuFnBXE4P4Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cSN6hYuFnBXE4P4Y p{margin:0;}#mermaid-svg-cSN6hYuFnBXE4P4Y g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-cSN6hYuFnBXE4P4Y g.classGroup text .title{font-weight:bolder;}#mermaid-svg-cSN6hYuFnBXE4P4Y .cluster-label text{fill:#333;}#mermaid-svg-cSN6hYuFnBXE4P4Y .cluster-label span{color:#333;}#mermaid-svg-cSN6hYuFnBXE4P4Y .cluster-label span p{background-color:transparent;}#mermaid-svg-cSN6hYuFnBXE4P4Y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cSN6hYuFnBXE4P4Y .cluster text{fill:#333;}#mermaid-svg-cSN6hYuFnBXE4P4Y .cluster span{color:#333;}#mermaid-svg-cSN6hYuFnBXE4P4Y .nodeLabel,#mermaid-svg-cSN6hYuFnBXE4P4Y .edgeLabel{color:#131300;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-cSN6hYuFnBXE4P4Y .label text{fill:#131300;}#mermaid-svg-cSN6hYuFnBXE4P4Y .labelBkg{background:#ECECFF;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-cSN6hYuFnBXE4P4Y .classTitle{font-weight:bolder;}#mermaid-svg-cSN6hYuFnBXE4P4Y .node rect,#mermaid-svg-cSN6hYuFnBXE4P4Y .node circle,#mermaid-svg-cSN6hYuFnBXE4P4Y .node ellipse,#mermaid-svg-cSN6hYuFnBXE4P4Y .node polygon,#mermaid-svg-cSN6hYuFnBXE4P4Y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cSN6hYuFnBXE4P4Y .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y g.clickable{cursor:pointer;}#mermaid-svg-cSN6hYuFnBXE4P4Y g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-cSN6hYuFnBXE4P4Y g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-cSN6hYuFnBXE4P4Y .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-cSN6hYuFnBXE4P4Y .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-cSN6hYuFnBXE4P4Y .dashed-line{stroke-dasharray:3;}#mermaid-svg-cSN6hYuFnBXE4P4Y .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-cSN6hYuFnBXE4P4Y #compositionStart,#mermaid-svg-cSN6hYuFnBXE4P4Y .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #compositionEnd,#mermaid-svg-cSN6hYuFnBXE4P4Y .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #dependencyStart,#mermaid-svg-cSN6hYuFnBXE4P4Y .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #dependencyStart,#mermaid-svg-cSN6hYuFnBXE4P4Y .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #extensionStart,#mermaid-svg-cSN6hYuFnBXE4P4Y .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #extensionEnd,#mermaid-svg-cSN6hYuFnBXE4P4Y .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #aggregationStart,#mermaid-svg-cSN6hYuFnBXE4P4Y .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #aggregationEnd,#mermaid-svg-cSN6hYuFnBXE4P4Y .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #lollipopStart,#mermaid-svg-cSN6hYuFnBXE4P4Y .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y #lollipopEnd,#mermaid-svg-cSN6hYuFnBXE4P4Y .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-cSN6hYuFnBXE4P4Y .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-cSN6hYuFnBXE4P4Y .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cSN6hYuFnBXE4P4Y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cSN6hYuFnBXE4P4Y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cSN6hYuFnBXE4P4Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建(宽接口)
恢复(宽接口)
保管(窄接口)
把快照交回原发器
Document
-text_ string
-cursor_ int
+create() : Memento
+restore(m) : void
<<不透明>>
Memento
-text_ string
-cursor_ int
History
-stack_ vector
+save(m) : void
+pop() : Memento

三个参与者(GoF 命名):

  • 原发器(Originator)Document------创建快照(把内部状态封进备忘录)、恢复快照(从备忘录写回),是唯一能解释快照内容的角色;
  • 备忘录(Memento):状态的黑盒封装------对看守者只暴露「存在」,对原发器暴露全部;
  • 看守者(Caretaker)History------保管快照的栈、队列或磁盘,负责在正确的时机把快照原样递回原发器,从头到尾不拆不看。

一个贴切的意象:备忘录是密封信。写信人将来还要读这封信(同一个原发器恢复),中间的邮差(看守者)只管投递保管------拆信越权,抄送更不可能。这个意象也预告了工程上的全部要点:信封要结实(快照不可变)、邮路要可靠(生命周期对齐)、信纸要够小(快照只存恢复必需的状态)。

4. 传统 C++ 写法(C++11 之前)

C++98 完整形态:备忘录做成原发器的嵌套类,构造私有、friend 授信------「宽窄接口」从图上的两个视图变成编译器强制的事实:

cpp 复制代码
// C++98/03 写法
#include <cstdio>
#include <string>
#include <vector>

class Document {
public:
  Document() : cursor_(0) {}

  void type(char c) {
    text_.insert(cursor_, 1, c);
    ++cursor_;
  }

  void backspace() {
    if (cursor_ == 0)
      return;
    text_.erase(cursor_ - 1, 1);
    --cursor_;
  }

  void print() const {
    printf("[%s|%d]\n", text_.c_str(),
           cursor_);
  }

  // ---- 备忘录:密封的状态快照 ----
  class Memento {
  public:
    ~Memento() {}

  private:
    friend class Document;    // 只有本体
                               // 能拆信
    Memento(const std::string& t, int c)
        : text_(t), cursor_(c) {}

    Memento(const Memento&);  // 禁复制:
    Memento& operator=(       // 快照不可变
        const Memento&);

    std::string text_;
    int cursor_;
  };

  Memento* create() const {
    return new Memento(text_, cursor_);
  }

  void restore(const Memento* m) {
    text_ = m->text_;         // 宽接口:
    cursor_ = m->cursor_;     // 只有这里能读
  }

private:
  std::string text_;
  int cursor_;
};

// ---- 看守者:只存、只传、不拆 ----
class History {
public:
  ~History() {
    for (size_t i = 0; i < stack_.size();
         ++i)
      delete stack_[i];
  }

  void save(Document::Memento* m) {
    stack_.push_back(m);
  }

  Document::Memento* pop() {
    if (stack_.empty())
      return 0;
    Document::Memento* m =
        stack_.back();
    stack_.pop_back();
    return m;
  }

private:
  std::vector<Document::Memento*> stack_;
};

int main() {
  Document doc;
  History hist;

  hist.save(doc.create());   // 存档 ""
  doc.type('a');
  doc.type('b');
  hist.save(doc.create());   // 存档 "ab"
  doc.type('c');
  doc.print();               // [abc|3]

  doc.restore(hist.pop());   // 回到 "ab"
  doc.print();               // [ab|2]
  return 0;
}

History 面对的是什么?一个只能 new 出来、delete 掉、拿来拿去的指针类型 ------它想把 text_ 读出来编译器都不答应。第 1 节路 A 的「封装陪葬」与路 B 的「拷贝语义含混」同时避开:哪些字段属于状态,只有 Document::create 一个地方说了算。

三条传统写法的铁律:宽接口只授给原发器 ------friend class Document 一行就是宽窄接口的全部实现,其他语言要用双接口/包可见性绕的圈,C++ 一词到位;快照不可变 ------构造之后无 setter,禁拷贝禁赋值(现代版可放宽为可移动),「邮差篡改历史」在类型层面消失;看守者零解释权 ------History 的全部代码里没有出现任何状态字段的名字,这行检查标准值得写进评审清单。

5. 现代 C++ 进阶写法

升级零:unique_ptr 管快照,移动语义流转。裸指针的创建/删除分工交给类型,快照成为移动专用对象:

cpp 复制代码
// 节选(Document 定义同第 4 节,略)
#include <memory>
#include <vector>

class History {
public:
  void save(
      std::unique_ptr<Document::Memento> m) {
    stack_.push_back(std::move(m));
  }

  std::unique_ptr<Document::Memento>
  pop() {
    if (stack_.empty())
      return nullptr;
    auto m = std::move(stack_.back());
    stack_.pop_back();
    return m;
  }

private:
  std::vector<
      std::unique_ptr<Document::Memento>>
      stack_;
};

// doc.restore(hist.pop().get());
// 或让 restore 直接接收 unique_ptr

Memento 的私有禁拷贝 hack 换成 = delete,其余纪律原样成立。

改进一:快照还是命令?三条撤销路线的成本曲线。第 19 篇与本篇合起来才是撤销的完整地图:

  • 命令路线(记操作):内存省(每步只存参数与逆操作),但要求操作规整可逆------适合绘图这类「操作有形状」的领域;
  • 快照路线(记状态):实现简单、绝对正确(不依赖「逆操作」的正确性),但状态大、步数深时内存爆炸------适合文档这类「自由编辑」的小状态对象;
  • 混合路线 (工业标配):每 N 步落一个快照,中间步骤用命令回放------撤销 M 步 = 回滚到最近快照再正向重放剩下的(N−M)步。数据库 WAL、游戏 checkpoint、编辑器的分档 undo 全是这条路线的变体:快照定锚点,命令补细粒度。

选型只看两个变量:状态大小 × 操作规整度------与第 12、13 篇「结构选型看变化轴」一脉相承。

改进二:值语义快照------封装边界的取舍。当状态全部是可拷贝的值(无句柄、无自指、无缓存),备忘录可以大方地退化为一个普通值对象:

cpp 复制代码
struct Snapshot {
  std::string text;
  int cursor;
};
std::vector<Snapshot> undo_;
// undo_.push_back({doc.text(), doc.cursor()});

friend、嵌套类、黑盒全部省掉------代价是「不透明」也没了:任何拿到 Snapshot 的代码都能读能改。取舍的界碑是模块边界 :同一个 .cpp 内部的轻量撤销,值快照最划算;跨模块、要长期保管、要序列化落盘的存档,回到密封信形态------黑盒不仅保护封装,也给了状态格式单独演化 的自由(改了 Document 内部布局,旧存档的解析只动原发器一个文件)。

展望 :快照内存问题还有第三条路------持久化数据结构(persistent data structure):不可变结构在「修改」时共享未变的子树,每份历史快照 O(1)~O(log n) 就地生成(immer 库是 C++ 代表)。它与第 15 篇享元同宗------共享不可变,省下的是「时间维度的重复」。

6. 优缺点与适用场景

  • ✅ 优点(GoF 后果清单):封装毫发无损 ------快照的读写全在原发器内,窄接口由编译器背书;状态恢复逻辑集中 ------「什么是可恢复的状态」只有一处定义,字段增减不惊动看守者;快照是可搬运的对象------进栈、跨线程、落盘、过网络皆可(游戏存档的骨架)。
  • ❌ 缺点:全量快照内存大 ------大对象深历史下不可持续,必须转向增量或混合路线;快照与恢复各有一次拷贝成本 ------高频快照在热路径上要掂量;含资源句柄的状态快照语义要逐字段定义 ------打开的文件、注册的回调、自指指针,「恢复」到底是重开、置空还是拒绝,备忘录不管,你得自己立约;看守者与快照的生命周期要对齐,泄漏与悬垂都会以「偶现崩溃」现身。
  • 🎯 适用场景:自由编辑型撤销(文本、表格单元格)、事务提交前的旧值保留、游戏存档与 checkpoint、试探性计算的回溯(N 皇后、搜索剪枝)、「草稿/发布」两态切换。

〔辨析〕备忘录 vs 命令(第 19 篇):撤销的两条路线------记状态与记操作;状态小而操作杂用备忘录,操作规整可逆用命令,大型系统用「快照锚点 + 命令回放」的混合;命令的 undo() 内部持一份「操作前小快照」也是常见的合体形态。备忘录 vs 直接值拷贝:push_back(*this) 是语言能力,备忘录是协议------选择性快照(哪些字段属于状态由本体说了算)+ 不透明保管(看守者无法越权)+ 封装不破;值拷贝把这三样全数交出去。备忘录 vs 数据库事务:undo log 与 WAL 是「备忘录 + 命令」的工业化合体,本篇改进一的三条路线在数据库内核里一个不缺。

7. 开源项目中的身影

标准库:std::exception_ptr 是异常的备忘录 。「把当时发生的异常原样保存、稍后恢复」------current_exception() 拍快照,rethrow_exception() 恢复,中间的代码只能保管传递,读不出内容

cpp 复制代码
#include <exception>

std::exception_ptr saved;
try {
  risky();
} catch (...) {
  // 拍下「当时」的异常快照
  saved = std::current_exception();
}

// ......稍后,甚至另一个线程
try {
  std::rethrow_exception(saved);  // 恢复
} catch (const std::exception& e) {
  // 原异常原样归来,类型信息无损
}

点评:三个角色严丝合缝------原发器是抛异常的栈(只有它知道异常的全部语义),备忘录是 exception_ptr,看守者是跨线程搬运它的任务队列/ future。窄接口窄到极致:除了一句「里面有个异常」,外界一无所知。这正是 C++11 把「异常」从「栈上的活动物」物化成「可保管对象」的方式。

Folly:exception_wrapper,给邮差开一点信封 。folly 对 exception_ptr 的工程化增强:保留搬运与重抛能力之外,允许有限地检视 ------get_exception<E>()what() 等只读查询:

cpp 复制代码
// 说明性片段(需包含 folly/ExceptionWrapper.h)
folly::exception_wrapper ew;
try {
  risky();
} catch (const std::system_error& e) {
  ew = folly::exception_wrapper(e);
}
if (ew.get_exception<std::system_error>()) {
  // 看得见类型,才能就地分流处理
}

点评:它站在「全黑盒」与「全公开」之间的工程折中点------纯搬运用 exception_ptr,搬运途中要按类型分流就得让邮差看一眼信封抬头。快照不透明到什么程度,从来是设计决定而不是教条。

AOSP:onSaveInstanceState(Bundle),系统当看守者 。Android 的 Activity 随时可能被系统回收,回收前框架调用 onSaveInstanceState 让应用把 UI 状态装进 Bundle;重建时原样交回由应用自行恢复(Java 侧协议,意图与 C++ 同构):

java 复制代码
// 说明性片段(Android 经典协议)
@Override
protected void onSaveInstanceState(
    Bundle outState) {
  super.onSaveInstanceState(outState);
  outState.putString("draft", draft);
  outState.putInt("cursor", pos);
}
// 进程被杀、界面重建,Bundle 原样奉还

点评:角色分配值得细品------原发器是 Activity(「哪些状态值得救」只有它知道,注意它 存视图内部的一切),看守者是系统(跨进程、跨生死周期保管 Bundle,从不解释内容),宽窄接口落在「系统只用 Bundle 的序列化协议,不读字段语义」。这也是快照路线的通用劝告:存「恢复必需的最小状态」,不是整个界面的复写

三份代码合看:异常快照把黑盒做到极致、folly 按需开缝、Android 让系统当邮差------「保管与解释分离」这一个核心动作,从类图一路延伸到了跨进程协议。

本篇小结

「回到过去」不必以封装为代价:把内部状态封进一封只有本体能拆的密封信,交给只会投递的看守者------宽窄接口在 C++ 里就是嵌套类加一行 friend,编译器替你守住越权的手。快照只存恢复必需的最小状态、构造后不可变、看守者零解释权,是三条不可让的纪律;状态大或历史深时,与命令模式合成「快照锚点 + 命令回放」的工业路线。值快照是模块内部的合理捷径,跨边界与要落盘的存档请回到黑盒。下一篇观察者模式------对象的历史存好了,「变化的消息」如何一对多地广播出去:23 种模式里知名度最高的那一个。

本文模式定义与角色划分参考了 Refactoring Guru《设计模式》中文版「备忘录」一章,意图译文、宽窄接口与两步增量快照的讨论参考了 GoF《Design Patterns》第 5 章 Memento 一节。

相关推荐
键盘会跳舞2 小时前
【C++多线程】thread_local 深度剖析:线程局部存储的生命周期与实现
c++·多线程·thread_local
whitelbwwww2 小时前
c++ 多线程
开发语言·c++·算法
努力努力再努力wz2 小时前
【Docker入门系列】:从架构演进到容器化:一文建立 Docker、虚拟化与 Namespace 的底层心智模型
运维·开发语言·数据结构·c++·docker·容器·架构
光电笑映2 小时前
Linux 线程编程:从进程、分页到线程控制与封装
linux·运维·服务器·c++
qinzechen2 小时前
本周科技行业热点汇总·2026第36周(2026年8月31日-9月6日)
c++·科技·算法
aqiu1111112 小时前
【C++ 代码分析与重构】常见 DFS 逻辑错误解析与修正
c++·重构·深度优先
zuozong_2 小时前
C++类与对象
开发语言·c++·算法
hansang_IR2 小时前
【代码】分层最短路模板
c++·算法·最短路
暖焰核心2 小时前
C++模板进阶——特化全解
javascript·c++·jquery