Boost库中Boost.PropertyTree使用和实战示例

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;
}

相关推荐
晨非辰26 分钟前
【数据结构】排序详解:从快速排序分区逻辑,到携手冒泡排序的算法效率深度评测
运维·数据结构·c++·人工智能·后端·深度学习·排序算法
lly20240629 分钟前
CSS3 分页技术解析
开发语言
CodeCraft Studio35 分钟前
国产化Excel开发组件Spire.XLS教程:Python将列表导出为CSV文件(含一维/二维/字典列表)
开发语言·python·excel·csv·spire.xls·列表导出为csv
guygg881 小时前
Alpha稳定分布概率密度函数的MATLAB实现
开发语言·matlab
草莓熊Lotso1 小时前
C++ 二叉搜索树(BST)完全指南:从概念原理、核心操作到底层实现
java·运维·开发语言·c++·人工智能·经验分享·c++进阶
oliveira-time2 小时前
单例模式中的饿汉式
java·开发语言
Go away, devil3 小时前
Java-----集合
java·开发语言
VBA63375 小时前
VBA即用型代码手册:利用函数保存为PDF文件UseFunctionSaveAsPDF
开发语言
say_fall5 小时前
C语言编程实战:每日刷题 - day2
c语言·开发语言·学习