CppCon 2017 学习:C++AND PERSISTENT MEMORY TECHNOLOGIES, LIKE INTEL‘S 3D-XPOINT

发布于:2025-06-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

C++ 和持久内存技术(Persistent Memory Technologies),比如英特尔的 3D XPoint。简单来说:

什么是持久内存?

  • 介于传统内存(RAM)和存储(SSD/HDD)之间的新型存储器件
  • 速度远快于SSD,且断电数据依然保留(非易失性)
  • 支持字节寻址,能像RAM一样直接访问数据

Intel 3D XPoint 简介

  • 一种非易失性存储技术
  • 比NAND闪存快1000倍
  • 支持更高耐久度和更低延迟
  • 可用于构建持久内存模块(比如Intel Optane DC Persistent Memory)

C++ 与持久内存的结合

  • 传统C++设计假设内存是易失的,程序重启后数据会丢失
  • 持久内存允许开发者将数据结构直接映射到非易失性内存区域
  • 需要特别的内存模型持久化语义,比如:
    • 保证写入操作完成后数据真的写入持久层(flush、fence等指令)
    • 崩溃后恢复一致的数据状态
  • 目前有专门的库(如Intel PMDK,Persistent Memory Development Kit)支持C++编程

使用挑战

  • 内存模型复杂,必须处理崩溃恢复和一致性
  • 需要新的编程范式,如事务性持久内存(Transactional Persistent Memory)
  • 标准C++还没有完全针对持久内存扩展,需要依赖第三方库

“Programming Model(编程模型)”有至少四个不同的含义:

  1. 硬件和软件之间的接口
    硬件如何向软件暴露其功能,比如寄存器、内存结构等。
  2. 指令集架构(ISA)
    处理器执行的机器指令集合,是硬件层面的编程模型。
  3. 操作系统向应用程序暴露的接口
    OS提供的系统调用、API等,应用程序通过它们访问硬件资源。
  4. 程序员的使用体验
    程序员如何感知和操作这个系统,包括编程语言、库、框架等。

这段内容是在描述**“Programming Model (meaning 3): Exposing to Applications”**,也就是操作系统如何通过不同层级向应用程序暴露持久内存(Persistent Memory)资源的方式。

大致结构是这样的:

  • 最底层是硬件,比如 NVDIMM(Non-Volatile Dual In-line Memory Module,持久内存条)
  • 由内核驱动(NVDIMM Driver)管理硬件设备
  • 在内核空间有 持久内存感知的文件系统(Persistent Memory Aware File System)
  • 这上面可能还有管理库(Management Library),帮助应用程序方便地操作持久内存
  • 应用程序通过标准文件API(File API)或者更底层的直接加载/存储(Load/Store Access)
  • DAX (Direct Access) 是一种允许应用程序跳过文件系统缓存,直接访问持久内存的机制,从而获得更高性能
    换句话说,这描述了持久内存如何通过文件系统和内核驱动,被暴露给用户态应用程序访问的层次和路径,体现了操作系统层面的编程模型。

你说的这段内容是关于持久内存(Persistent Memory)写入路径数据持久性保证机制,尤其是在CPU缓存和内存之间如何确保数据在断电时依然持久保存。

大致流程和关键点如下:

  • MOV 指令把数据送到核心(Core)
  • 数据经过CPU的多个缓存层级:L1, L2, L3缓存
  • 为了保证数据持久写入内存,需要用特定指令和机制刷新缓存:
    • CLWB + fence:Cache Line Write Back + 内存屏障,保证缓存行写回到内存,且写操作顺序正确
    • CLFLUSH:清除缓存行,使数据写入内存
    • NT stores + fence:非临时(Non-Temporal)写操作 + 屏障,直接绕过缓存写入内存
    • WBINVD(仅内核可用):写回并使缓存无效,强制刷新所有缓存
  • **ADR (Asynchronous DRAM Refresh)机制和WPQ (Write Pending Queue)**机制帮助在电源断电时保护内存中的数据,防止数据丢失
  • 最终数据写入到DIMM(内存模块)
    总结:这体现了从CPU核心发起数据写入操作,到数据穿越缓存层级,最后安全写入到持久内存的过程,以及各种保证数据不会因断电而丢失的机制。

你这段是关于持久内存(Persistent Memory)写入的原子性和持久化刷新的流程示例,以及崩溃恢复时数据一致性的问题。

大致步骤和重点:

  1. open(…); — 打开文件或持久内存设备
  2. mmap(…); — 将文件或持久内存映射到进程地址空间,得到指针 pmem
  3. strcpy(pmem, “Hello, World!”); — 把字符串写入映射的持久内存区域(数据写入CPU缓存,尚未真正持久化)
  4. pmem_persist(pmem, 14); — 显式调用持久化函数(通常会调用 clwb + fence 或 clflush + fence 等指令)将缓存中的数据刷新到持久内存中,保证数据持久化
  5. crash — 系统或程序崩溃时,如果 pmem_persist 没有被调用,写入的数据可能仍停留在缓存中,导致断电或崩溃后数据丢失或不一致
    重点: 仅写入内存地址不保证数据持久,必须调用类似 pmem_persist 的函数显式刷新缓存,才能确保数据安全写入持久内存,避免崩溃后数据丢失。
    如果不做正确的刷新,可能导致“部分写入”或“脏数据”,破坏数据的原子性和一致性。

持久内存写入的原子性(atomicity)问题,特别是跨越8字节(64位)存储单元时可能发生的不一致状态

背景

  • 持久内存的刷新是基于缓存行(一般是64字节)单位。
  • CPU对内存的写操作通常是以8字节(64位)为原子单位,写入的数据如果跨越了两个8字节单元,写入过程就可能被中断(比如发生崩溃)。
  • 因此,即使调用了 pmem_persist 来确保缓存刷写,也可能导致数据“半写”状态。

你列举的几种崩溃后可能的持久数据状态:

选项 描述 说明
1. \0\0\0\0\0\0\0\0\0\0... 全是零,写入失败或没开始写
2. Hello, W\0\0\0\0\0\0... 部分写入,字符串截断
3. \0\0\0\0\0\0\0\0orld!\0 前半部分没写,后半部分写入
4. Hello, \0\0\0\0\0\0\0\0 部分写入,未完成
5. Hello, World!\0 完整写入,理想情况

核心问题:

当写操作跨越8字节边界且发生崩溃,数据可能处于不一致的中间状态,这是持久内存开发时需要注意的。

如何保证原子性?

  • 尽量避免跨越8字节边界的写操作。
  • 使用事务性写入机制(比如 Intel的Persistent Memory Development Kit中的事务支持)。
  • 设计日志机制(write-ahead logging)或双缓冲等方案来确保崩溃恢复后数据一致。
  • 使用硬件支持的原子持久化操作(如果有)。

持久内存(Persistent Memory,PM)编程模型的分层结构和目标,特别是面向NVDIMM这类硬件的应用场景。

结构层次(从上到下):

  1. Application
    开发者编写的应用程序,直接使用持久内存提供的能力。
  2. Tools
    开发工具(调试器、分析器、库等),帮助开发者更高效、安全地开发。
  3. Standard
    语言标准、持久内存规范,定义一致的接口和行为。
  4. Load/Store & File API
    支持直接内存读写(Load/Store)或文件式访问(File API)。
  5. Language Runtime & Libraries
    运行时环境和库,封装复杂细节,提供持久内存支持。
  6. PM-Aware MMU (Memory Management Unit)
    支持持久内存的内存管理单元,实现地址映射与保护。
  7. File System & Mappings
    专为持久内存设计的文件系统和内存映射,保证数据一致性和性能。
  8. Kernel Space
    操作系统内核,提供底层管理和安全保障。

目标和结果:

  • 让持久内存编程更安全、减少错误
  • 让开发者使用起来更自然、符合语言习惯(idiomatic)
  • 支持多种常用语言,实现统一、高效的编程体验

相关硬件:

  • NVDIMM(Non-Volatile Dual Inline Memory Module):带持久性功能的内存模块,支持断电后数据保持。

**持久内存(persistent memory)自定义分配器(pallocator)**实现的 std::vector 用法,重点在于:

关键点:

  • 使用了 pvector = std::vector<p<int>, pallocator>,也就是用带有持久内存感知的分配器 pallocator,分配持久化内存中的对象。
  • persistent_vector 是分配在持久内存中的 vector,数据不会随进程结束而丢失。
  • 你调用 push_back(42) 向向量添加元素。
  • 不需要显式调用 flush(刷新缓存),这意味着分配器或底层库自动保证了数据的持久性。
  • 操作是事务性的(transactional),保证一致性和原子性(即使崩溃,数据不会半更新)。
  • C++ 库本身(配合持久内存支持的运行时和分配器)完成了持久化细节的管理,让程序员体验和普通 std::vector 类似。

总结:

这就是利用持久内存的现代C++抽象,让你用熟悉的容器接口操作持久化数据,而不必关心刷新缓存、写屏障等复杂细节,保证安全和高效。

你给出的代码是一个用 shared_ptr 实现的 简单线程不安全的链表队列示例,适合做持久内存(Persistent Memory)相关说明:

术语:

  • Persistent Memory/Storage Class Memory/Non-Volatile Memory
    快速、可按字节寻址且断电数据不会丢失的内存技术。
  • Pool
    进程虚拟地址空间中一块连续的持久内存区域。

代码分析:

struct entry {
    std::shared_ptr<entry> next;
    int value;
};
std::shared_ptr<entry> head;
std::shared_ptr<entry> tail;
void push(int value) {
    auto n = std::make_shared<entry>(value, nullptr);
    if (head == nullptr) {
        head = tail = n;
    } else {
        tail->next = n;
        tail = n;
    }
}
int pop() {
    if (head == nullptr)
        throw std::runtime_error("Nothing to pop");
    auto ret = head->value;
    head = head->next;
    if (head == nullptr)
        tail = nullptr;
    return ret;
}
  • push 添加新节点到队尾。
  • pop 从队头移除节点并返回值。
  • shared_ptr 管理节点生命周期,自动释放。

与持久内存的联系:

  • 在持久内存上下文,这样的链表结构需要用持久内存分配器(比如 pallocator)和持久指针(比如 p_ptr)替代 shared_ptr,保证断电后数据有效。
  • 需要考虑事务性和一致性,防止崩溃时数据结构损坏。

你这段代码展示了基于**事务(transaction)**的持久内存操作,结合了持久内存库(比如 PMDK)提供的事务机制,实现原子且持久的链表入队和出队。

代码结构解析

void push(int value)
{
    transaction::exec_tx(pool, [this, &value] {
        auto n = make_shared<entry>(value, nullptr);
        if (head == nullptr) {
            head = tail = n;
        } else {
            tail->next = n;
            tail = n;
        }
    });
    // 事务作用域结束后,自动提交或回滚
}
int pop()
{
    int ret;
    transaction::exec_tx(pool, [this, &ret] {
        if (head == nullptr)
            throw runtime_error("Nothing to pop");
        ret = head->value;
        head = head->next;
        if (head == nullptr)
            tail = nullptr;
    });
    return ret;
}
  • transaction::exec_tx(pool, lambda)
    开启一个事务,对pool中的数据进行原子操作。
  • 事务内所有改动要么全成功提交,要么全部回滚,保证持久内存中数据的一致性。
  • pushpop 都在事务里执行,保证链表结构不会因为意外断电而破坏。

事务特性

  • Undo log:操作前记录旧状态,失败时回滚。
  • ACID属性:原子性、一致性、隔离性、持久性。
  • 可嵌套:支持事务内嵌套事务。
  • 崩溃恢复:中断时,下一次打开pool时自动回滚或完成未完成事务。
  • :事务执行期间持有锁,保证数据完整性。

关联

  • pool 是持久内存的管理池,类似数据库中的事务日志。
  • make_shared 可能是持久内存友好的版本,比如 make_persistent

这段内容讲的是持久内存中**手动事务(Manual Transaction)**的用法,和自动事务(如 transaction::exec_tx)相比,手动事务更灵活但需要显式提交。

手动事务核心示例

auto pop = pool<root>::open("/path/to/poolfile", "layout string");
{
    transaction::manual(pop, persistent_mtx, persistent_shmtx);
    // 在事务内做工作,修改持久内存数据
    transaction::commit();  // 必须显式调用提交事务
}
// 此处锁会释放
auto aborted = transaction::get_last_tx_error();  // 检查是否有事务中断或失败

关键点总结

  • 显式提交
    与自动事务不同,手动事务必须调用 transaction::commit() 显式提交,否则会自动回滚。
  • 事务范围
    事务开始于 transaction::manual(),结束于 transaction::commit() 或作用域结束时自动回滚。
  • 锁机制
    可以传入一个或多个持久内存相关的锁,保证多线程下数据一致性。
  • RAII风格
    结合C++的 RAII 资源管理概念,确保事务期间资源(锁)被正确管理。
  • 异常处理
    默认会在异常或忘记提交时自动回滚,不会抛出异常以通知事务失败,而是通过 get_last_tx_error() 获取错误信息。
  • 多锁支持
    允许传入任意数量的持久内存驻留锁以控制访问。

用途

手动事务适合:

  • 复杂事务逻辑需要多个步骤,中间可能抛异常。
  • 需要对事务提交时机有完全控制。
  • 多锁协作控制更精细。

持久内存库中**自动事务(Automatic Transactions)闭包事务(Closure Transactions)**的用法和区别。

自动事务 (Automatic Transaction) 核心示例

auto pop = pool<root>::open("/path/to/poolfile", "layout string");
try {
    transaction::automatic(pop, persistent_mtx, persistent_shmtx);
    // 在事务范围内进行工作,自动开始事务
    // 不需要显式调用 commit()
} catch (...) {
    // 处理事务失败或异常
}
auto aborted = transaction::get_last_tx_error();  // 查询事务是否失败
特点:
  • 不需要显式 commit,事务会在作用域结束时自动提交(无异常)或回滚(有异常)。
  • 依赖 C++17std::uncaught_exceptions 来判断异常状态。
  • 锁自动在事务结束时释放。
  • 事务体内异常会被捕获,事务回滚,但不会直接抛出异常。
  • 与手动事务功能和语义基本一致,但更简洁。

闭包事务 (Closure Transaction) 核心示例

auto pop = pool<root>::open("/path/to/poolfile", "layout string");
transaction::exec_tx(pop, [] {
    // 在这里写事务体代码
}, persistent_mtx, persistent_shmtx);
auto aborted = transaction::get_last_tx_error();
特点:
  • 事务体用 std::function 闭包表示,传给 exec_tx
  • 不需要显式 commit,事务由框架自动管理。
  • 兼容所有支持 C++11 的编译器。
  • 事务失败时抛出异常,需要用户捕获。
  • 接受任意数量的锁,保证多线程安全。
  • 相比自动事务更“现代”、更优雅。
  • 使用闭包方式,代码更简洁,更安全。

总结对比

特性 手动事务 自动事务 闭包事务
事务提交 需要显式 commit() 自动提交或回滚 自动提交或回滚
异常处理 不抛异常,查询错误码 不抛异常,查询错误码 抛异常,需要捕获
语言标准 C++11 或更高 C++17 C++11
锁管理 手动传入,多锁支持 手动传入,多锁支持 手动传入,多锁支持
使用便利性 需要管理事务生命周期 RAII 自动管理事务生命周期 函数式调用,简洁明了

使用持久内存(如 Intel 的 PMDK)实现队列结构时,如何正确处理持久性数据的一些细节。我们重点来解释下面这两个关键词和它们的关系:

1. struct entry 中的持久化成员

最初版本:

struct entry {
    shared_ptr<entry> next;
    int value;
};

这个结构是可以正常用在堆上的,但不具备持久性(crash 之后数据就丢了)。如果你把它用于持久内存中,就必须考虑 如何正确保存数据(snapshot)

2. p<T> 类型的使用:持久内存中的“快照”工具

为了让字段能在事务中持久化,就得使用 p<T> 类型来包裹它:

struct entry {
    shared_ptr<entry> next;
    p<int> value;  // 使用 p<> 进行事务快照持久化
};

什么是 p<T>

这是 PMDK 提供的模板类,目的是让你能方便地对基本类型(如 int, bool, float, …)实现事务内的快照+回滚机制

它能做什么?
  • 重载 operator=:自动在赋值时记录旧值,事务失败就可以回滚。
  • 重载算术/逻辑运算符:如 +, -, *, ==, &&,等价于普通类型的行为。
  • 事务开始时,值被自动“快照”。
  • 事务提交成功后,变更写入 NVDIMM。
  • 事务失败/中断时,值被恢复。
注意点:
  • p<T> 适用于基本类型
  • 不适合结构体或类,因为 . 运算符不能被重载 → 无法深层追踪变更。
  • 如果你有复杂类型,需要自己写事务管理逻辑或用 persistent_ptr<> 来包装整块内存。

为什么 shared_ptr<entry> 不需要用 p<>

因为你通常会改的是 value 字段,而不是 next 指针本身。而且:

  • 对象的分配/删除本身是在事务控制下完成的。
  • 指针可以用 make_persistent<entry>()delete_persistent() 管理持久性。
  • 如果你要追踪指针的值变更(很少这样做),才可能考虑 p<persistent_ptr<entry>>

总结

元素 是否需要持久化支持? 工具
基本类型字段(如 int) p<T>
指针指向的对象 make_persistent<T>()
指针本身 否(通常) 事务包裹的修改已处理
聚合类型(struct) 不直接用 p<T> 用 persistent_ptr + 自定义逻辑

你已经掌握了持久化队列实现中的一个关键点:部分持久化 vs. 完全持久化。我们来快速回顾并总结其中的概念和差异:

1. Partially Persistent Entry

struct entry {
    shared_ptr<entry> next;  // 常规指针(非持久)
    p<int> value;            // 持久值(snapshot 支持)
};

特点:

  • 只有 value 是持久化的,通过 p<int> 实现。
  • next 是普通的 shared_ptr,在程序崩溃后无法恢复链表结构
  • 适用于只需要保证数据值持久,但不在崩溃后恢复结构的场景。

2. Fully Persistent Entry

struct entry {
    persistent_ptr<entry> next;  // 持久化指针
    p<int> value;                // 持久化数据
};

特点:

  • 整个结构可持久化并能在重启后恢复
  • persistent_ptr<T> 是 PMDK 提供的智能指针,它:
    • 在池中是一个偏移地址,启动时映射回内存。
    • 在崩溃后自动恢复正确地址。
  • 结构可完全恢复链表状态。

总结:p<T> vs persistent_ptr<T>

特性 p<T> persistent_ptr<T>
目的 快照基本类型(int/float/bool…) 持久化对象指针
生命周期管理 不负责 不自动管理(需 make_persistent 等)
持久化作用范围 只影响数据字段 用于构建和恢复对象关系(链表、树等)
崩溃恢复后是否能恢复结构 否(靠 runtime rebuild) 是(可直接 deref 重建关系)
事务中是否自动追踪 是(自动快照) 否(需手动将目标字段加到事务)
是否支持 polymorphism 否(不能用于虚函数或继承)

结论

如果你要构建一个在崩溃之后依然能恢复数据结构的持久化队列,你必须使用:

struct entry {
    persistent_ptr<entry> next;
    p<int> value;
};

并且所有 entry 的创建和销毁都需要在事务中通过:

  • make_persistent<entry>()
  • delete_persistent<entry>()

现在看到的是将一个持久化队列push() 操作完整地改写为使用 PMDK 的事务机制和持久化内存分配。我们来逐步解释一下背后的关键点:

持久化队列节点结构

struct entry {
    persistent_ptr<entry> next;
    p<int> value;
};

意义:

  • persistent_ptr<entry>:指向下一个持久节点(指针本身持久)。
  • p<int>:持久化的整数值,自动在事务中进行快照。

push() 方法演进回顾

初始版(非持久):

auto n = make_shared<entry>(value, nullptr);  // 非持久分配
  • 使用的是 shared_ptr,数据不保存在 NVDIMM 中,程序崩溃后会丢失。

持久化版:

auto n = make_persistent<entry>(value, nullptr);
  • 使用 make_persistent 分配的对象位于持久化内存中。
  • n 是一个 persistent_ptr<entry>,会在事务中追踪其分配。

完整 push() 方法解析:

void push(pool_base &pool, int value)
{
    transaction::exec_tx(pool, [this] {
        auto n = make_persistent<entry>(value, nullptr);  // 分配在 PMEM 中
        if (head == nullptr) {
            head = tail = n;  // 初始化空队列
        } else {
            tail->next = n;   // 修改 tail
            tail = n;
        }
    });
}

事务特性:

  • 所有对 PMEM 的更改(分配节点、修改指针)都在事务中。
  • 如果中途崩溃,要么完全回滚,要么全部完成。
  • 事务保证原子性,避免队列处于“半插入”状态。

关键补充知识

概念 说明
make_persistent<T>() 在 PMEM 上分配一个 T 类型对象。必须在事务中使用。
persistent_ptr<T> 智能指针,指向 PMEM 中的对象,支持偏移恢复。
transaction::exec_tx() PMDK 的事务包装器,提供强一致性。
pool_base &pool 持久化内存池的句柄,事务必须绑定这个对象。

现在已经完成了对 持久化队列的 pop() 操作 的理解,从最初的内存结构到完整事务处理逻辑。我们来系统性地总结一下关键点,以巩固知识。

持久化 Pop 方法

int pop(pool_base &pool) {
    transaction::exec_tx(pool, [this] {
        if (head == nullptr)
            throw runtime_error("Nothing to pop");
        auto ret = head->value;               // 取出值
        auto tmp_entry = head->next;          // 暂存下一个节点
        delete_persistent<entry>(head);       // 删除当前节点
        head = tmp_entry;                     // 移动头指针
        if (head == nullptr)
            tail = nullptr;                   // 如果为空,重置 tail
        return ret;                           // 返回值
    });
}

关键点解析

项目 说明
transaction::exec_tx() 所有操作都在事务中,确保 crash-safe。
delete_persistent<entry>(head) 在 PMEM 中销毁对象(调用析构 + 回收)。
make_persistent() push() 中对称使用的构造器。
persistent_ptr 持久指针类型,用于定位 PMEM 中对象。
pool_base &pool 表示当前持久化内存池的句柄,事务需要。
head == nullptr 检查队列是否空,避免非法访问。

事务性分配函数总结(PMDK 专属)

函数 作用
make_persistent<T>() 在事务中分配并构造 PMEM 上的对象
delete_persistent<T>() 在事务中析构并释放 PMEM 上的对象
特性 - 事务感知(分配失败自动回滚)
- 必须在 exec_tx() 或其他事务上下文中使用

提示

  • 由于 persistent_ptr 不自动管理对象生命周期,你必须手动调用 delete_persistent
  • 每次 push() 使用 make_persistent 创建新节点,因此 pop() 必须负责删除节点。
  • 所有成员变量和分配对象都应位于持久化内存池中(通常从 root 对象开始)。
    如果你还想要:
  • 添加日志记录机制;
  • 改造成线程安全版本(带 persistent_mutex);
  • 支持队列持久化恢复测试;

持久化内存中的同步原语(如 mutex)与标准容器(如 std::map, std::vector)的适配方式。以下是结构化总结,帮助你清晰掌握这部分内容。

Persistent Memory Synchronization

支持的同步类型(PMDK 提供)

类型 描述
mutex 类似 std::mutex,适用于独占访问
shared_mutex 类似 std::shared_mutex,适用于读多写少
timed_mutex 支持超时锁
condition_variable 同步多个线程等待事件
接口兼容 std 可以直接用在 std::lock_guard 等中
自动重新初始化 crash 后重新挂载 pool 时自动恢复锁状态
可用于事务中 可和 transaction::exec_tx() 协同使用

Persistent Allocator:为容器准备

提供内容(标准接口兼容)

成员函数 功能描述
allocate() 分配持久化内存
deallocate() 释放持久化内存
construct() 调用构造函数
destroy() 显式析构
max_size() 支持最大分配尺寸查询
rebind<T>() 为泛型容器重新绑定类型
使用 persistent_ptr 所有内存引用都通过它管理(重要!)
只能在事务内用 避免内存泄漏或状态不一致

为啥标准容器几乎直接能用?

因为:

  • 有了 persistent_ptr
  • 有了持久化 allocator
  • 容器如 std::vector, std::map 只需小改就可支持 PMEM!

std::map 与持久化场景

原始结构:

template <class _VoidPtr>
class __tree_node_base {
    pointer __right_;
    __parent_pointer __parent_;
    bool __is_black_;
};

问题:

  • __is_black_ 是关键元数据,但 不是持久化类型
  • 如果崩溃后恢复,会 丢失红黑树结构信息

改造方式(Injecting p<>)

改造后(PMEM 适配版):

template <class _VoidPtr>
class __tree_node_base {
    typedef typename __rebind_persistency_type<pointer, bool>::type bool_type;
    bool_type __is_black_; // 实际上就是 p<bool>
};

p<T>

  • 自动重载赋值(operator=);
  • 自动在事务中 做 snapshot
  • 提供几乎所有算术和逻辑操作;
  • 用于基本类型:bool, int, float 等。

总结一句话:

通过 persistent_ptrp<T> 的结合,再配合持久化 allocator,就可以把 std 容器改造为 crash-consistent 持久化容器。

如果你希望了解:

  • 如何自己封装一个 std::map 持久化版本
  • 如何同步多个持久化队列
  • 或者如何用 condition_variable 实现持久化生产者-消费者

网站公告

今日签到

点亮在社区的每一天
去签到