Cereal中支持QString、QVector、QList、QMap
在Cereal中支持Qt容器序列化
🌟 Cereal序列化库对Qt容器的完美支持
关键词:Cereal
Qt序列化
QString
QMap
跨平台数据交换
核心代码
namespace cereal
{#if 0//序列化在xml下会多生成一个节点// 为 QString 定义序列化函数template <class Archive>void load(Archive& archive, QString& str) {// 转换为 std::string 以兼容 cerealstd::string utf8Str;archive(utf8Str);str = QString::fromStdString(utf8Str);}template <class Archive>void save(Archive& archive, const QString& str) {// 转换为 std::string 以兼容 cerealstd::string utf8Str = str.toStdString();archive(utf8Str);}
#else// 避免XML中生成<value0>节点//! saving string to xmlvoid CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive& ar, QString const& str){ar.saveValue(str.toStdString());}//! loading string from xmlvoid CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive& ar, QString& str){std::string temp;ar.loadValue(temp);str = QString::fromStdString(temp);}
#endif// 保存 QVectortemplate <class Archive, typename T>typename std::enable_if<!Archive::is_loading::value, void>::typeserialize(Archive& ar, QVector<T>& vec) {ar(make_size_tag(vec.size())); // number of elementsfor (auto&& item : vec) {ar(item);}}// 加载 QVectortemplate <class Archive, typename T>typename std::enable_if<Archive::is_loading::value, void>::typeserialize(Archive& ar, QVector<T>& vec) {size_type size;ar(make_size_tag(size));vec.resize(size);for (auto&& item : vec) {ar(item);}}// 保存 QListtemplate <class Archive, typename T>typename std::enable_if<!Archive::is_loading::value, void>::typeserialize(Archive& ar, QList<T>& list) {ar(make_size_tag(list.size())); // number of elementsfor (auto const& item : list) {ar(item);}}// 加载 QListtemplate <class Archive, typename T>typename std::enable_if<Archive::is_loading::value, void>::typeserialize(Archive& ar, QList<T>& list) {size_type size;ar(make_size_tag(size));std::list<T> list_temp;list_temp.resize(size);for (auto&& item : list_temp) {ar(item);}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)list = QList<T>(list_temp.begin(), list_temp.end());
#elselist = QList<T>::fromStdList(list_temp);
#endif}// 保存 QMaptemplate <class Archive, typename K, typename V>void save(Archive& ar, const QMap<K, V>& map) {ar(make_size_tag(static_cast<size_type>(map.size()))); // 保存元素数量for (auto it = map.constBegin(); it != map.constEnd(); ++it) {// 显式构造键值对并命名ar(make_map_item(it.key(), it.value()));}}// 加载 QMaptemplate <class Archive, typename K, typename V>void load(Archive& ar, QMap<K, V>& map) {size_type size;ar(make_size_tag(size)); // 读取元素数量map.clear();for (size_t i = 0; i < size; ++i) {K key;V value;ar(make_map_item(key, value)); // 按顺序加载键值对map.insert(key, value); // 插入到 QMap}}
};
🔧 问题背景
Cereal作为轻量级C++11序列化库,默认支持STL容器但不直接兼容Qt容器(如QString
、QVector
等)。当序列化Qt类型时,开发者需手动实现适配逻辑,否则会导致:
- 冗余嵌套节点(如XML中的
<value0>
) - 编码错误(未处理UTF-8转换)
- 容器大小丢失(动态容器反序列化失败)
⚙️ Qt容器序列化实现方案
1. QString的序列化优化
通过特化XMLInputArchive/XMLOutputArchive
避免冗余节点:
namespace cereal {// 避免XML中生成<value0>节点void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive& ar, QString const& str) {ar.saveValue(str.toStdString()); // 直接写入字符串值}void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive& ar, QString& str) {std::string temp;ar.loadValue(temp);str = QString::fromStdString(temp); // UTF-8安全转换}
}
关键点:
saveValue/loadValue
直接操作原始字节流,跳过对象包装逻辑- 使用
toStdString()
而非toUtf8()
保证跨平台兼容性(Qt6默认UTF-8)
2. 动态容器序列化(QVector/QList)
通过make_size_tag
标记容器大小,SFINAE区分加载/保存逻辑:
// QVector序列化
template <class Archive, typename T>
typename std::enable_if<!Archive::is_loading::value>::type
serialize(Archive& ar, QVector<T>& vec) {ar(make_size_tag(vec.size())); for (auto& item : vec) ar(item); // 逐项序列化
}// QVector反序列化
template <class Archive, typename T>
typename std::enable_if<Archive::is_loading::value>::type
serialize(Archive& ar, QVector<T>& vec) {size_type size;ar(make_size_tag(size));vec.resize(size);for (auto& item : vec) ar(item); // 按大小逐项加载
}
QList特殊处理:
// Qt5.14+使用迭代器构造(避免fromStdList废弃)
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)list = QList<T>(temp_list.begin(), temp_list.end());
#elselist = QList<T>::fromStdList(temp_list);
#endif
3. 关联容器序列化(QMap)
解决QMap
迭代器返回值而非键值对的问题:
// 保存QMap
template <class Archive, typename K, typename V>
void save(Archive& ar, QMap<K, V>& map) {ar(make_size_tag(map.size()));for (auto it = map.constBegin(); it != map.constEnd(); ++it) {ar(make_map_item(it.key(), it.value())); // 显式分离键值}
}// 加载QMap
template <class Archive, typename K, typename V>
void load(Archive& ar, QMap<K, V>& map) {size_type size;ar(make_size_tag(size));map.clear();for (size_t i = 0; i < size; ++i) {K key; V value;ar(make_map_item(key, value)); // 按序加载键值map.insert(key, value); // 插入唯一键}
}
对比STL实现差异:
特性 | std::map | QMap 适配方案 |
---|---|---|
迭代方式 | 直接获取std::pair | 需constBegin()/constEnd() |
键唯一性处理 | 自动排序 | insert() 自动去重 |
性能优化 | emplace_hint | 线性插入 |
⚠️ 注意事项
-
类型安全
- 键类型
K
必须实现operator<
(QMap排序依赖) - 嵌套容器(如
QMap<QString, QVector<int>>
)需递归支持序列化
- 键类型
-
错误处理
try {ar(map); } catch (cereal::Exception& e) {qDebug() << "序列化失败:" << e.what(); }
-
扩展建议
- 支持
QHash
:仿照QMap
实现,替换迭代器为constBegin/constEnd
- 多态类型:通过
CEREAL_REGISTER_TYPE
注册派生类
- 支持
💎 总结
本文提供了Cereal序列化库对Qt核心容器(QString
、QVector
、QList
、QMap
)的完整适配方案,重点解决:
- XML格式下冗余节点问题 → 特化XML存档函数
- 动态容器大小保存 →
make_size_tag
标签 - Qt-STL迭代器差异 → 显式键值分离处理
通过标准C++模板特化机制,实现了Qt与Cereal的无缝协作,为跨平台数据交换提供轻量化解决方案。
扩展阅读:Cereal官方文档 | C++ Cereal序列化库的使用
Cereal宏 一行声明序列化函数