yaml-cpp для C++: полное руководство с примерами
1. Что это и зачем нужна
yaml-cpp — это легковесная, современная библиотека на C++ для парсинга и генерации YAML-файлов. YAML (YAML Ain't Markup Language) — это формат сериализации данных, удобный для чтения человеком, который часто используется для конфигурационных файлов, описания структур данных, CI/CD пайплайнов (например, GitHub Actions, GitLab CI) и обмена данными между сервисами. В отличие от JSON, YAML поддерживает комментарии, многострочные строки, якоря и алиасы, что делает его идеальным для конфигураций.
Зачем использовать yaml-cpp? Если вы пишете на C++ приложение, которому нужно читать конфигурационные файлы (например, настройки сервера, параметры модели машинного обучения, описание зависимостей), yaml-cpp предоставляет простой и типобезопасный API. Библиотека полностью соответствует спецификации YAML 1.2, поддерживает все типы данных (скаляры, последовательности, отображения) и позволяет легко преобразовывать YAML-документы в структуры C++ и обратно.
Ключевые преимущества: минимальные зависимости (только стандартная библиотека C++11/14/17), header-only опциональная сборка, поддержка Unicode, возможность кастомной сериализации для пользовательских типов, и активное сообщество (более 5 тысяч звезд на GitHub).
2. Установка
yaml-cpp не устанавливается через пакетные менеджеры языков (pip, npm). Для C++ используются системные менеджеры пакетов или сборка из исходников.
Linux (Debian/Ubuntu)
sudo apt-get install libyaml-cpp-dev
macOS (Homebrew)
brew install yaml-cpp
Windows (vcpkg)
vcpkg install yaml-cpp
Сборка из исходников (CMake)
git clone https://github.com/jbeder/yaml-cpp.git
cd yaml-cpp
mkdir build && cd build
cmake -DYAML_BUILD_SHARED_LIBS=ON ..
make
sudo make install
После установки подключите библиотеку в CMakeLists.txt:
find_package(yaml-cpp REQUIRED)
target_link_libraries(my_project yaml-cpp)
3. Быстрый старт — минимальный рабочий пример
Создайте файл config.yaml:
server:
host: "localhost"
port: 8080
debug: true
Прочитаем его с помощью yaml-cpp:
#include <iostream>
#include <yaml-cpp/yaml.h>
int main() {
// Загружаем YAML файл
YAML::Node config = YAML::LoadFile("config.yaml");
// Читаем значения
std::string host = config["server"]["host"].as<std::string>();
int port = config["server"]["port"].as<int>();
bool debug = config["debug"].as<bool>();
std::cout << "Host: " << host << "\n";
std::cout << "Port: " << port << "\n";
std::cout << "Debug: " << std::boolalpha << debug << "\n";
return 0;
}
Компиляция: g++ -std=c++17 main.cpp -lyaml-cpp -o app
4. Основные методы/функции/классы
4.1. YAML::Node — основной класс для работы с узлами
Сигнатура: class YAML::Node
Описание: Представляет любой элемент YAML-документа: скаляр, последовательность (массив) или отображение (словарь). Поддерживает итерацию, доступ по ключу/индексу, проверку типа.
YAML::Node node;
node["name"] = "Alice";
node["age"] = 30;
node["hobbies"].push_back("reading");
node["hobbies"].push_back("coding");
std::cout << node["name"].as<std::string>() << "\n"; // Alice
std::cout << node["hobbies"][0].as<std::string>() << "\n"; // reading
4.2. YAML::LoadFile() — загрузка из файла
Сигнатура: YAML::Node YAML::LoadFile(const std::string& filename)
Описание: Парсит YAML-файл и возвращает корневой узел. При ошибке парсинга выбрасывает исключение YAML::BadFile или YAML::ParserException.
try {
YAML::Node doc = YAML::LoadFile("settings.yaml");
// работа с doc
} catch (const YAML::Exception& e) {
std::cerr << "YAML Error: " << e.msg << "\n";
}
4.3. YAML::Load() — загрузка из строки
Сигнатура: YAML::Node YAML::Load(const std::string& input)
Описание: Парсит YAML из строки. Полезно для тестирования или получения данных из сети.
std::string yaml_str = "key: value\nlist: [1, 2, 3]";
YAML::Node node = YAML::Load(yaml_str);
std::cout << node["key"].as<std::string>() << "\n"; // value
std::cout << node["list"][1].as<int>() << "\n"; // 2
4.4. Node::as<T>() — преобразование в тип C++
Сигнатура: template<typename T> T Node::as() const
Описание: Преобразует узел в указанный тип. Поддерживает: int, double, std::string, bool, std::vector, std::map, и пользовательские типы (через специализацию).
YAML::Node node = YAML::Load("value: 42");
int val = node["value"].as<int>(); // 42
// Преобразование в вектор
YAML::Node arr = YAML::Load("[1, 2, 3]");
std::vector<int> vec = arr.as<std::vector<int>>(); // {1, 2, 3}
4.5. Node::IsScalar(), IsSequence(), IsMap() — проверка типа узла
Сигнатура: bool Node::IsScalar() const, bool Node::IsSequence() const, bool Node::IsMap() const
Описание: Проверяют, является ли узел скаляром (строка/число/буль), последовательностью (массивом) или отображением (словарем). Позволяют безопасно обрабатывать динамические структуры.
YAML::Node node = YAML::Load("mixed: [1, {a: 2}]");
if (node["mixed"].IsSequence()) {
for (auto it = node["mixed"].begin(); it != node["mixed"].end(); ++it) {
if (it->IsScalar()) {
std::cout << "Scalar: " << it->as<int>() << "\n";
} else if (it->IsMap()) {
std::cout << "Map key 'a': " << (*it)["a"].as<int>() << "\n";
}
}
}
4.6. Node::push_back() — добавление элемента в последовательность
Сигнатура: void Node::push_back(const Node& value)
Описание: Добавляет элемент в конец последовательности. Если узел не был последовательностью, он становится таковой (даже если был скаляром).
YAML::Node list;
list.push_back("first");
list.push_back(2);
list.push_back(3.14);
std::cout << list.size() << "\n"; // 3
std::cout << list[1].as<int>() << "\n"; // 2
4.7. Node::remove() — удаление элемента
Сигнатура: void Node::remove(const Node& key) (для map), void Node::remove(std::size_t index) (для sequence)
Описание: Удаляет элемент по ключу (для отображений) или индексу (для последовательностей). После удаления индексы перестраиваются.
YAML::Node config = YAML::Load("{a: 1, b: 2, c: 3}");
config.remove("b"); // удаляем ключ 'b'
std::cout << config["c"].as<int>() << "\n"; // 3
YAML::Node arr = YAML::Load("[10, 20, 30]");
arr.remove(1); // удаляем элемент с индексом 1 (значение 20)
std::cout << arr[1].as<int>() << "\n"; // 30
4.8. YAML::Emitter — генерация YAML
Сигнатура: class YAML::Emitter
Описание: Позволяет создавать YAML-строку программно. Поддерживает отступы, комментарии, стили (блоковый/потоковый).
YAML::Emitter out;
out << YAML::BeginMap;
out << YAML::Key << "name";
out << YAML::Value << "Bob";
out << YAML::Key << "age";
out << YAML::Value << 25;
out << YAML::Key << "skills";
out << YAML::Value << YAML::BeginSeq;
out << "C++" << "Python" << "YAML";
out << YAML::EndSeq;
out << YAML::EndMap;
std::cout << out.c_str() << "\n";
// Вывод:
// name: Bob
// age: 25
// skills:
// - C++
// - Python
// - YAML
4.9. YAML::Node::operator[] — доступ по ключу/индексу
Сигнатура: Node Node::operator[](const std::string& key) и Node Node::operator[](std::size_t index)
Описание: Предоставляет доступ к дочернему узлу. Если ключ не существует, создается новый пустой узел. Для последовательностей работает как доступ по индексу.
YAML::Node doc = YAML::Load("{user: {name: Alice}}");
doc["user"]["age"] = 30; // создаем новый ключ
std::cout << doc["user"]["age"].as<int>() << "\n"; // 30
YAML::Node list = YAML::Load("[a, b, c]");
list[3] = "d"; // расширяет массив
std::cout << list[3].as<std::string>() << "\n"; // d
4.10. YAML::Dump() — сериализация узла в строку
Сигнатура: std::string YAML::Dump(const Node& node)
Описание: Преобразует узел обратно в YAML-строку. Полезно для логирования или сохранения измененной конфигурации.
YAML::Node node;
node["title"] = "My App";
node["version"] = 1.0;
node["features"].push_back("auth");
node["features"].push_back("logging");
std::string yaml_output = YAML::Dump(node);
std::cout << yaml_output;
// Вывод:
// features:
// - auth
// - logging
// title: My App
// version: 1.0
5. Реальный практический пример (100-200 строк кода)
Создадим утилиту для управления конфигурацией веб-сервера: чтение, валидация, обновление и сохранение.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <yaml-cpp/yaml.h>
struct ServerConfig {
std::string host;
int port;
bool ssl;
std::vector<std::string> allowed_origins;
int max_connections;
};
// Пользовательская конвертация для ServerConfig
namespace YAML {
template<>
struct convert<ServerConfig> {
static Node encode(const ServerConfig& rhs) {
Node node;
node["host"] = rhs.host;
node["port"] = rhs.port;
node["ssl"] = rhs.ssl;
node["allowed_origins"] = rhs.allowed_origins;
node["max_connections"] = rhs.max_connections;
return node;
}
static bool decode(const Node& node, ServerConfig& rhs) {
if (!node.IsMap()) return false;
rhs.host = node["host"].as<std::string>();
rhs.port = node["port"].as<