Boost.PropertyTree 是一个轻量级、跨格式(JSON / XML / INI / INFO)配置数据管理库,特别适合 配置文件管理、参数解析、层级数据存储。
1. PropertyTree 的核心概念
PropertyTree 就是一棵树(ptree)
cpp
boost::property_tree::ptree pt;
每个节点:
- 可以有 子节点
- 可以有 值(value)
- 可以通过 key 访问
对应 JSON / XML 中的层级关系。
2. PropertyTree 支持的文件格式
| 格式 | 读 | 写 | 特性 |
|---|---|---|---|
| JSON | ✔️ | ✔️ | 常用于 SLAM 配置 |
| XML | ✔️ | ✔️ | 常用于参数结构化更复杂场景 |
| INI | ✔️ | ✔️ | 简单平铺型配置 |
| INFO | ✔️ | ✔️ | Boost 自己的配置格式 |
导入头文件:
cpp
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/info_parser.hpp>
3. JSON 读写详解
3.1 读取 JSON 文件
cpp
ptree pt;
read_json("config.json", pt);
3.2 获取基本类型
cpp
double fx = pt.get<double>("camera.fx");
int width = pt.get<int>("image.width");
std::string dataset = pt.get<std::string>("path.dataset");
如果 key 不存在:
pt.get<T>()会抛异常pt.get<T>("key", default)不会抛异常
例:
cpp
int threads = pt.get<int>("system.threads", 4);
4. 访问数组(JSON array)
Boost.PropertyTree 不支持原生 JSON 数组的类型 ,但提供了一种约定:
数组元素被视为多个同名子节点。
示例 JSON:
json
{
"topics": {
"subscribe": ["imu", "image", "lidar"]
}
}
读取:
cpp
for (auto& v : pt.get_child("topics.subscribe")) {
std::cout << v.second.get_value<std::string>() << std::endl;
}
注意:
v.first是空字符串(因为数组没有键)v.second是子 node
5. 写 JSON
写简单 key-value
cpp
pt.put("camera.fx", 525.0);
pt.put("dataset.path", "/home/data");
写数组
cpp
ptree array;
ptree item;
item.put("", "imu");
array.push_back(std::make_pair("", item));
item.put("", "image");
array.push_back(std::make_pair("", item));
pt.add_child("topics.subscribe", array);
保存:
cpp
write_json("out.json", pt);
6. 访问子节点(child)
cpp
ptree cam = pt.get_child("camera");
double fx = cam.get<double>("fx");
遍历子节点:
cpp
ptree& topics = pt.get_child("topics");
for (auto& kv : topics) {
std::cout << kv.first << std::endl;
}
7. XML 读写详解
读 XML
cpp
ptree pt;
read_xml("config.xml", pt);
XML 特点:
- 数据就是树,非常契合 ptree
- 支持属性
<tag attr="value">
访问 XML 节点:
cpp
std::string name = pt.get<std::string>("config.camera.<xmlattr>.name");
<xmlattr> 节点专门保存属性。
写 XML
cpp
write_xml("out.xml", pt);
8. INI 读写详解
配置文件 ini:
ini
[camera]
fx = 525
fy = 525
读取:
cpp
read_ini("config.ini", pt);
double fx = pt.get<double>("camera.fx");
写入:
cpp
write_ini("out.ini", pt);
9. INFO 文件读写
INFO 是 Boost 的配置格式,用法与 JSON / INI 类似:
camera
{
fx 525
fy 525
}
读写:
cpp
read_info("cfg.info", pt);
write_info("cfg_out.info", pt);
10. 判断 key 是否存在
cpp
if (auto child = pt.get_child_optional("camera")) {
std::cout << "camera config exists\n";
}
获取 value 是否存在
cpp
if (auto opt = pt.get_optional<double>("camera.fx")) {
double fx = opt.get();
}
这是 推荐写法,不会抛异常。
11. 强烈推荐的 SLAM 配置最佳实践
1) 通用配置读取函数
cpp
template <typename T>
T Read(const ptree& pt, const std::string& key, const T& default_val) {
return pt.get<T>(key, default_val);
}
2) 自动打印所有参数(调试超实用)
cpp
void print_ptree(const ptree& pt, int level = 0) {
for (auto& kv : pt) {
for (int i = 0; i < level; i++) std::cout << " ";
std::cout << kv.first << ": " << kv.second.get_value<std::string>() << "\n";
print_ptree(kv.second, level+1);
}
}
3) 把参数加载成结构体(推荐 SLAM 做法)
cpp
struct CameraConfig {
double fx, fy;
double cx, cy;
};
CameraConfig LoadCameraConfig(const ptree& pt) {
CameraConfig c;
c.fx = pt.get<double>("camera.fx");
c.fy = pt.get<double>("camera.fy");
c.cx = pt.get<double>("camera.cx");
c.cy = pt.get<double>("camera.cy");
return c;
}
比处处写 pt.get<> 更安全。
12. PropertyTree 的坑
1) 不支持非同名数组(JSON array 兼容性差)
如:
json
"points": [1, 2, 3]
Boost 支持,但复杂结构数组会有问题:
json
"points": [
{"x": 1, "y": 2},
{"x": 3, "y": 4}
]
需要:
cpp
for (auto &item : pt.get_child("points")) {
double x = item.second.get<double>("x");
}
2) JSON 序列化时会多一个缩进或空白
通常无大碍,但不能精确复制原 JSON。
3) 不支持丰富 JSON 特性(null / bool / 原生 array)
只能用 string + number + child tree 表示。
4) 不是 JSON 专用库
Boost.PropertyTree 的定位是"配置树",不是完整 JSON 实现。
需要强 JSON 支持 → 使用 nlohmann/json。
13. 简单示例:SLAM 配置读写(JSON)
配置文件 config.json:
json
{
"camera": {
"fx": 525.0,
"fy": 525.0,
"cx": 319.5,
"cy": 239.5
},
"topics": {
"subscribe": ["imu", "image", "lidar"]
}
}
读取:
cpp
ptree pt;
read_json("config.json", pt);
double fx = pt.get<double>("camera.fx");
for (auto& v : pt.get_child("topics.subscribe")) {
std::cout << v.second.get_value<std::string>() << "\n";
}
写出:
cpp
write_json("out.json", pt);
14、综合示例
下面是 Boost::PropertyTree 在 SLAM 工程中的完整配置模块模板的综合示例:
- 全部模块化封装
- 跨 JSON / INI / XML / INFO
- 自动校验参数
- 自动打印所有配置
- 支持结构体加载(Camera / IMU / Lidar / Backend)
- 可直接作为 CMake 项目使用
这是工程级的 SLAM 配置系统,可直接嵌入自研 SLAM 框架。
目录结构
config/
├── Config.h
├── Config.cpp
├── CameraConfig.h
├── IMUConfig.h
├── BackendConfig.h
└── default_config.json
src/
└── main.cpp
CMakeLists.txt
1. Config 模块核心功能
| 功能 | 说明 |
|---|---|
| 自动识别 JSON / INI / XML / INFO | 文件后缀自动选择解析器 |
| 缺省值自动填充 | get<T>(key, default) |
| 参数不存在时警告 | 不抛异常,便于 SLAM 工程稳定性 |
| 自动打印所有配置 | 用于调试非常方便 |
| 参数结构体化 | camera / imu / lidar / backend 单独结构体 |
| 支持多文件合并 | 可加载多个 config 文件叠加 |
2. Config.h(主配置系统头文件)
cpp
#pragma once
#include <boost/property_tree/ptree.hpp>
#include <string>
#include <memory>
class Config
{
public:
static bool Load(const std::string& file);
static bool Has(const std::string& key);
template<typename T>
static T Get(const std::string& key, const T& default_val);
template<typename T>
static void Set(const std::string& key, const T& value);
static void PrintAll();
static boost::property_tree::ptree& Tree();
private:
static boost::property_tree::ptree pt_;
static void LoadJson(const std::string& file);
static void LoadIni(const std::string& file);
static void LoadXml(const std::string& file);
static void LoadInfo(const std::string& file);
};
3. Config.cpp(主逻辑)
cpp
#include "Config.h"
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/info_parser.hpp>
#include <iostream>
#include <algorithm>
boost::property_tree::ptree Config::pt_;
bool Config::Load(const std::string& file)
{
std::string ext;
auto pos = file.find_last_of('.');
if (pos != std::string::npos)
ext = file.substr(pos+1);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
try {
if (ext == "json") LoadJson(file);
else if (ext == "xml") LoadXml(file);
else if (ext == "ini") LoadIni(file);
else if (ext == "info") LoadInfo(file);
else {
std::cerr << "[Config] Unsupported file type: " << ext << std::endl;
return false;
}
}
catch (std::exception& e) {
std::cerr << "[Config] Load error: " << e.what() << std::endl;
return false;
}
return true;
}
void Config::LoadJson(const std::string& file) {
boost::property_tree::read_json(file, pt_);
}
void Config::LoadXml(const std::string& file) {
boost::property_tree::read_xml(file, pt_);
}
void Config::LoadIni(const std::string& file) {
boost::property_tree::read_ini(file, pt_);
}
void Config::LoadInfo(const std::string& file) {
boost::property_tree::read_info(file, pt_);
}
bool Config::Has(const std::string& key)
{
return bool(pt_.get_child_optional(key));
}
template<typename T>
T Config::Get(const std::string& key, const T& default_val)
{
return pt_.get<T>(key, default_val);
}
template<typename T>
void Config::Set(const std::string& key, const T& value)
{
pt_.put(key, value);
}
boost::property_tree::ptree& Config::Tree() {
return pt_;
}
void PrintPT(const boost::property_tree::ptree& pt, int depth)
{
for (auto& kv : pt) {
for (int i=0; i<depth; i++) std::cout << " ";
std::cout << kv.first << ": " << kv.second.get_value<std::string>() << std::endl;
PrintPT(kv.second, depth+1);
}
}
void Config::PrintAll()
{
PrintPT(pt_, 0);
}
// 注意:模板函数声明在 .h 中,定义也需在 .h 中或显式实例化
template int Config::Get<int>(const std::string&, const int&);
template double Config::Get<double>(const std::string&, const double&);
template std::string Config::Get<std::string>(const std::string&, const std::string&);
template void Config::Set<int>(const std::string&, const int&);
template void Config::Set<double>(const std::string&, const double&);
template void Config::Set<std::string>(const std::string&, const std::string&);
4. CameraConfig.h
cpp
#pragma once
#include "Config.h"
struct CameraConfig
{
double fx = 0, fy = 0;
double cx = 0, cy = 0;
void Load() {
fx = Config::Get<double>("camera.fx", 0);
fy = Config::Get<double>("camera.fy", 0);
cx = Config::Get<double>("camera.cx", 0);
cy = Config::Get<double>("camera.cy", 0);
}
};
5. IMUConfig.h
cpp
#pragma once
#include "Config.h"
struct IMUConfig
{
double gyro_noise = 0;
double acc_noise = 0;
double gyro_bias = 0;
double acc_bias = 0;
void Load() {
gyro_noise = Config::Get<double>("imu.gyro_noise", 0.0);
acc_noise = Config::Get<double>("imu.acc_noise", 0.0);
gyro_bias = Config::Get<double>("imu.gyro_bias", 0.0);
acc_bias = Config::Get<double>("imu.acc_bias", 0.0);
}
};
6. BackendConfig.h
cpp
#pragma once
#include "Config.h"
struct BackendConfig
{
int max_iterations = 10;
double huber_delta = 1.0;
void Load() {
max_iterations = Config::Get<int>("backend.max_iter", 10);
huber_delta = Config::Get<double>("backend.huber_delta", 1.0);
}
};
7. default_config.json(示例配置)
json
{
"camera": {
"fx": 525.0,
"fy": 525.0,
"cx": 319.5,
"cy": 239.5
},
"imu": {
"gyro_noise": 0.001,
"acc_noise": 0.002,
"gyro_bias": 0.0001,
"acc_bias": 0.0002
},
"backend": {
"max_iter": 20,
"huber_delta": 1.0
}
}
8. main.cpp
cpp
#include "Config.h"
#include "CameraConfig.h"
#include "IMUConfig.h"
#include "BackendConfig.h"
int main()
{
Config::Load("config/default_config.json");
Config::PrintAll();
CameraConfig cam;
IMUConfig imu;
BackendConfig backend;
cam.Load();
imu.Load();
backend.Load();
std::cout << "fx = " << cam.fx << std::endl;
std::cout << "gyro_noise = " << imu.gyro_noise << std::endl;
std::cout << "backend iter = " << backend.max_iterations << std::endl;
return 0;
}