Die Boost C++ Bibliotheken

Kapitel 63. Boost.ProgramOptions

Boost.ProgramOptions ist eine Bibliothek, die es einfach macht, Kommandozeilenparameter zu parsen. Der Einsatz von Boost.ProgramOptions ergibt daher nur dann Sinn, wenn Sie Kommandozeilenparameter parsen möchten. Dies ist üblicherweise in Konsolenanwendungen der Fall. Entwickeln Sie beispielsweise eine Windows-Anwendung mit grafischer Benutzeroberfläche, spielen Kommandozeilenparameter womöglich keine große Rolle.

Boost.ProgramOptions teilt die Aufgabe, Kommandozeilenparameter zu parsen, in drei Schritte ein:

  1. Sie definieren Kommandozeilenparameter. Sie legen deren Namen fest und geben an, ob ein zusätzlicher Wert zum Namen auf der Kommandozeile angegeben werden muss. Falls der Kommandozeilenparameter als Name/Wert-Paar interpretiert wird, bestimmen Sie außerdem über einen Typ, welcher Art der Wert sein muss – also zum Beispiel ein String oder eine Zahl.

  2. Sie verwenden einen Parser, dem Sie die Beschreibung Ihrer Kommandozeilenparameter und die Kommandozeile übergeben, mit der Ihre Anwendung gestartet wurde. Letztere erhalten Sie über zwei Parameter von main(), die üblicherweise argc und argv genannt werden.

  3. Sie speichern die vom Parser ausgewerteten Kommandozeilenparameter ab. Boost.ProgramOptions bietet hierzu eine Klasse an, die von std::map abgeleitet ist. Kommandozeilenparameter werden also als Name/Wert-Paare gespeichert. Anschließend können Sie überprüfen, welche Parameter gespeichert wurden und auf welche Werte sie gesetzt sind.

Beispiel 63.1 zeigt, wie Sie grundsätzlich vorgehen müssen, um Kommandozeilenparameter mit Boost.ProgramOptions zu parsen.

Beispiel 63.1. Grundsätzliche Vorgehensweise bei Boost.ProgramOptions
#include <boost/program_options.hpp>
#include <iostream>

using namespace boost::program_options;

void on_age(int age)
{
  std::cout << "On age: " << age << '\n';
}

int main(int argc, const char *argv[])
{
  try
  {
    options_description desc{"Options"};
    desc.add_options()
      ("help,h", "Help screen")
      ("pi", value<float>()->default_value(3.14f), "Pi")
      ("age", value<int>()->notifier(on_age), "Age");

    variables_map vm;
    store(parse_command_line(argc, argv, desc), vm);
    notify(vm);

    if (vm.count("help"))
      std::cout << desc << '\n';
    else if (vm.count("age"))
      std::cout << "Age: " << vm["age"].as<int>() << '\n';
    else if (vm.count("pi"))
      std::cout << "Pi: " << vm["pi"].as<float>() << '\n';
  }
  catch (const error &ex)
  {
    std::cerr << ex.what() << '\n';
  }
}

Wenn Sie Boost.ProgramOptions verwenden möchten, reicht es, die Headerdatei boost/program_options.hpp einzubinden. Sie können dann auf alle Klassen und Funktionen dieser Bibliothek im Namensraum boost::program_options zugreifen.

Sie verwenden die Klasse boost::program_options::options_description, um Kommandozeilenparameter zu beschreiben. Da Sie eine Instanz dieser Klasse direkt auf einen Stream wie std::cout ausgeben können, um eine Beschreibung aller Kommandozeilenparameter anzuzeigen, können Sie dem Konstruktor einen Namen übergeben – also eine Art Überschrift für Ihre Kommandozeilenparameter.

Die Klasse boost::program_options::options_description besitzt eine Methode add(), die Sie aufrufen könnten, um die Beschreibung eines Kommandozeilenparameters in Form eines Objekts vom Typ boost::program_options::option_description zu übergeben. Möchten Sie viele Kommandozeilenparameter beschreiben, müssten Sie jedoch add() wiederholt aufrufen. Im Beispiel 63.1 wird stattdessen auf die Methode add_options() zugegriffen, die es einfacher macht, mehrere Kommandozeilenparameter zu definieren.

add_options() gibt ein Proxy-Objekt zurück, das die Instanz von boost::program_options::options_description repräsentiert. Der eigentliche Typ des Proxy-Objekts ist unwichtig. Interessanter ist, dass dieses Proxy-Objekt die Definition einer großen Zahl von Kommandozeilenparametern vereinfacht. So ist der Operator operator() derart überladen, dass Sie direkt die zur Definition eines Kommandozeilenparameters notwendigen Angaben übergeben können. Außerdem gibt dieser Operator eine Referenz auf das gleiche Proxy-Objekt zurück, so dass Sie mehrmals hintereinander auf operator() zugreifen können.

Im Beispiel 63.1 werden mit Hilfe des Proxy-Objekts drei Kommandozeilenparameter definiert. Der erste Kommandozeilenparameter ist --help. Der Beschreibungstext des Parameters lautet Help screen. Es handelt sich bei diesem Kommandozeilenparameter um einen Schalter, nicht um ein Name/Wert-Paar. --help ist auf der Kommandozeile entweder angegeben oder nicht. Hinter --help muss und kann keine zusätzliche Angabe vorgenommen werden.

Beachten Sie, dass der erste an operator() übergebene String help,h lautet. Sie können für Kommandozeilenparameter eine Kurzversion angeben. Diese darf nur aus einem Buchstaben bestehen und wird hinter ein Komma gesetzt. Die Hilfe soll also nicht nur über --help, sondern auch über -h aufgerufen werden können.

Neben --help sind zwei weitere Kommandozeilenparameter definiert: --pi und --age. Diese unterscheiden sich von --help insofern, als dass es sich nicht um Schalter, sondern um Name/Wert-Paare handelt. Sowohl --pi als auch --age erwarten eine zusätzliche Angabe auf der Kommandozeile.

Um einen Kommandozeilenparameter als Name/Wert-Paar zu definieren, übergeben Sie als zweiten Parameter an operator() einen Zeiger auf ein Objekt vom Typ boost::program_options::value_semantic. Sie greifen dabei nicht direkt auf diese Klasse zu, sondern verwenden die Hilfsfunktion boost::program_options::value(). Diese erstellt ein Objekt vom Typ boost::program_options::value_semantic und gibt den Zeiger auf dieses zurück, den Sie direkt über operator() an das Proxy-Objekt weiterreichen.

Bei boost::program_options::value() handelt es sich um eine Template-Funktion. Sie übergeben den Typ des Werts, der dem Kommandozeilenparameter zugewiesen werden können soll, als Template-Parameter. Der Kommandozeilenparameter --age kann demnach auf eine Ganzzahl gesetzt werden, während für --pi eine Kommazahl akzeptiert wird.

Beachten Sie, dass das von boost::program_options::value() zurückgegebene Objekt einige nützliche Methoden anbietet. So können Sie zum Beispiel über default_value() eine Standardeinstellung vornehmen. Im Beispiel 63.1 ist --pi auf 3,14 gesetzt, wenn der Kommandozeilenparameter nicht explizit angegeben und die Standardeinstellung überschrieben wird.

Über notifier() kann eine Funktion mit dem Wert eines Parameters verknüpft werden. Dieser Funktion wird der entsprechende Wert übergeben, der von der Kommandozeile gelesen wurde. Auf diese Weise kann ein Wert zusätzlich verarbeitet werden. So ist im Beispiel 63.1 on_age() mit --age verknüpft. Wenn der Kommandozeilenparameter --age verwendet wird, um ein Alter anzugeben, wird dieses an die Funktion on_age() übergeben, von wo es auf die Standardausgabe ausgegeben wird.

Beachten Sie, dass die Verarbeitung eines Werts über eine Funktion wie on_age() optional ist. Sie müssen notifier() nicht verwenden, da Sie auch auf eine andere Weise an Parameterwerte gelangen.

Nachdem alle Kommandozeilenparameter definiert sind, verwenden Sie einen Parser. Boost.ProgramOptions stellt mit boost::program_options::parse_command_line() eine Hilfsfunktion zur Verfügung, der im Beispiel 63.1 mit argc und argv die Kommandozeile und mit desc die Definition der Kommandozeilenparameter übergeben wird. boost::program_options::parse_command_line() gibt die geparsten Kommandozeilenparameter in einem Objekt vom Typ boost::program_options::parsed_options zurück, auf das Sie gewöhnlich aber nicht zugreifen. Stattdessen übergeben Sie das Objekt direkt an boost::program_options::store(), um die geparsten Kommandozeilenparameter in einem Container abzulegen.

Im Beispiel 63.1 wird an boost::program_options::store() als zweiter Parameter vm übergeben. Es handelt sich dabei um ein Objekt vom Typ boost::program_options::variables_map. Diese Klasse ist von std::map<std::string, boost::program_options::variable_value> abgeleitet und bietet daher grundsätzlich die gleichen Methoden wie std::map an. So können Sie zum Beispiel mit count() überprüfen, ob ein bestimmter Kommandozeilenparameter verwendet wurde und im Container abgespeichert ist.

Bevor jedoch im Beispiel 63.1 auf vm zugegriffen und count() aufgerufen wird, wird die Funktion boost::program_options::notify() verwendet. Dies ist notwendig, damit Funktionen wie on_age(), die mit notifier() mit einem Parameter verknüpft wurden, aufgerufen werden. Ohne boost::program_options::notify() würde kein Aufruf von on_age() stattfinden.

Wenn Sie auf vm zugreifen, können Sie nicht nur mit count() überprüfen, ob ein Kommandozeilenparameter existiert. Sie können auch auf den Wert zugreifen, der dem Kommandozeilenparameter zugewiesen ist. Der Wert hat den Typ boost::program_options::variable_value – eine Klasse, die intern auf boost::any basiert. Sie können das Objekt vom Typ boost::any über eine Methode value() direkt erhalten.

Im Beispiel 63.1 wird nicht value() aufgerufen, sondern as(). Diese Methode gibt den entsprechenden Wert mit dem Typ zurück, den Sie an as() übergeben. as() greift auf boost::any_cast() zu, um die entsprechende Typumwandlung vorzunehmen.

Achten Sie darauf, dass der Typ, den Sie an as() übergeben, mit dem Typ des Parameterwerts übereinstimmt. Wenn wie im Beispiel 63.1 hinter dem Kommandozeilenparameter --age eine Ganzzahl vom Typ int angegeben werden muss, müssen Sie entsprechend int als Template-Parameter an as() übergeben.

Sie können Beispiel 63.1 auf unterschiedliche Weise ausführen. Rufen Sie das Programm zum Beispiel wie folgt auf:

test

In diesem Fall wird Pi: 3.14 ausgegeben. Auch wenn --pi nicht als Kommandozeilenparameter übergeben wird, ist dieser Kommandozeilenparameter immer vorhanden, nachdem im Programm ein Standardwert für Pi angegeben wurde.

Sie können den Parameterwert wie folgt überschreiben:

test --pi 3.1415

Die Ausgabe lautet nun Pi: 3.1415.

Geben Sie zusätzlich ein Alter an:

test --pi 3.1415 --age 29

Sie erhalten On age: 29 und Age: 29. Die erste Zeile wird ausgegeben, wenn im Programm boost::program_options::notify() aufgerufen wird, weil dann on_age() ausgeführt wird. Eine Ausgabe für --pi erhalten Sie nur deswegen nicht, weil im Beispiel else if verwendet wird und nur der erste gefundene Kommandozeilenparameter eine Aktion auslöst.

Rufen Sie nun die Hilfe auf:

test -h

Sie erhalten daraufhin eine vollständige Beschreibung aller Kommandozeilenparameter:

Options:
  -h [ --help ]         Help screen
  --pi arg (=3.1400001) Pi
  --age arg             Age

Sie können die Hilfe auf zweierlei Art und Weise aufrufen, da es eine Kurzversion des entsprechenden Kommandozeilenparameters gibt. Für --pi wird der Standardwert angezeigt.

Die Formatierung der Kommandozeilenparameter und ihrer Beschreibungen erfolgt automatisch. Sie müssen lediglich wie im Beispiel 63.1 das Objekt vom Typ boost::program_options::options_description auf die Standardausgabe ausgeben.

Rufen Sie das Beispiel nun wie folgt auf:

test --age

Sie erhalten folgende Ausgabe:

the required argument for option '--age' is missing.

Da hinter --age kein Wert angegeben ist, wirft der Parser, der in der Funktion boost::program_options::parse_command_line() zur Anwendung kommt, eine Ausnahme vom Typ boost::program_options::error. Diese Ausnahme wird im Beispiel abgefangen, um eine Meldung auf die Standardfehlerausgabe auszugeben.

boost::program_options::error ist von std::logic_error abgeleitet. Boost.ProgramOptions definiert weitere Ausnahmeklassen, die alle Kindklassen von boost::program_options::error sind. So könnte im Beispiel auch eine Ausnahme vom Typ boost::program_options::invalid_syntax abgefangen werden. Dies ist der exakte Typ der Ausnahme, die im Falle eines fehlenden Werts hinter dem Kommandozeilenparameter --age geworfen wird.

Nachdem Sie den grundsätzlichen Aufbau eines Programms kennengelernt haben, um mit Boost.ProgramOptions Kommandozeilenparameter zu parsen, lernen Sie im Beispiel 63.2 weitere Einstellungsmöglichkeiten kennen.

Beispiel 63.2. Spezielle Einstellungsmöglichkeiten mit Boost.ProgramOptions
#include <boost/program_options.hpp>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>

using namespace boost::program_options;

void to_cout(const std::vector<std::string> &v)
{
  std::copy(v.begin(), v.end(), std::ostream_iterator<std::string>{
    std::cout, "\n"});
}

int main(int argc, const char *argv[])
{
  try
  {
    int age;

    options_description desc{"Options"};
    desc.add_options()
      ("help,h", "Help screen")
      ("pi", value<float>()->implicit_value(3.14f), "Pi")
      ("age", value<int>(&age), "Age")
      ("phone", value<std::vector<std::string>>()->multitoken()->
        zero_tokens()->composing(), "Phone")
      ("unreg", "Unrecognized options");

    command_line_parser parser{argc, argv};
    parser.options(desc).allow_unregistered().style(
      command_line_style::default_style |
      command_line_style::allow_slash_for_short);
    parsed_options parsed_options = parser.run();

    variables_map vm;
    store(parsed_options, vm);
    notify(vm);

    if (vm.count("help"))
      std::cout << desc << '\n';
    else if (vm.count("age"))
      std::cout << "Age: " << age << '\n';
    else if (vm.count("phone"))
      to_cout(vm["phone"].as<std::vector<std::string>>());
    else if (vm.count("unreg"))
      to_cout(collect_unrecognized(parsed_options.options,
        exclude_positional));
    else if (vm.count("pi"))
      std::cout << "Pi: " << vm["pi"].as<float>() << '\n';
  }
  catch (const error &ex)
  {
    std::cerr << ex.what() << '\n';
  }
}

Im Beispiel 63.2 werden ähnliche Kommandozeilenparameter geparst wie zuvor. Es gibt jedoch einige Unterschiede. So wird zum Beispiel für den Parameterwert von Pi nicht mehr default_value(), sondern implicit_value() aufgerufen. Das bedeutet, dass Pi nicht mehr standardmäßig auf 3,14 gesetzt ist. Der Kommandozeilenparameter --pi muss gesetzt werden, damit Pi definiert ist. Wird implicit_value() verwendet, muss der Kommandozeilenparameter jedoch nicht auf einen Wert gesetzt werden. Es reicht, wenn --pi angegeben wird, ohne dass hinter dem Parameternamen ein Wert gesetzt wird. Dann ist Pi mit 3,14 definiert.

Der Funktion boost::program_options::value() wird für --age ein Zeiger auf eine Variable age übergeben. Auf diese Weise kann ein Parameterwert in einer Variablen gespeichert werden. Selbstverständlich steht der Parameterwert auch weiterhin im Container vm zur Verfügung.

Beachten Sie, dass der Parameterwert nur dann in der Variablen age gespeichert wird, wenn die Funktion boost::program_options::notify() aufgerufen wird. Diese Funktion wird nicht nur dann benötigt, wenn notifier() verwendet wird. Es ist grundsätzlich empfehlenswert, boost::program_options::notify() aufzurufen, nachdem mit boost::program_options::store() geparste Kommandozeilenparameter gespeichert wurden, da es andernfalls zu unvorhergesehenen Problemen kommen kann.

Beispiel 63.2 unterstützt mit --phone einen neuen Kommandozeilenparameter, um eine Telefonnummer an das Programm zu übergeben. Genauer gesagt sollen beliebig viele Telefonnummern an das Programm übergeben werden können. So soll das Programm zum Beispiel wie folgt aufgerufen werden können, um die beiden Telefonnummern 123 und 456 zu übergeben:

test --phone 123 456

Beispiel 63.2 unterstützt die Angabe mehrerer Telefonnummern auf diese Weise, weil für den Parameterwert multitoken() aufgerufen wurde. Da außerdem zero_tokens() aufgerufen wird, ist es auch möglich, --phone ohne Angabe einer Telefonnummer zu verwenden.

Das Beispielprogramm unterstützt auch folgende Möglichkeit zur Angabe mehrerer Telefonnummern:

test --phone 123 --phone 456

Auch in diesem Fall werden die beiden Telefonnummern 123 und 456 geparst. Der Grund ist, dass für den Parameterwert zusätzlich composing() aufgerufen wurde. Es ist dann erlaubt, einen Kommandozeilenparameter mehrfach zu verwenden – die Werte werden zusammengeführt.

Beachten Sie, dass der Typ für den Parameterwert für --phone std::vector<std::string> ist. Sie benötigen einen Container, damit mehrere Telefonnummern gespeichert werden können.

Mit --unreg ist im Beispiel 63.2 ein weiterer Kommandozeilenparameter definiert. Es handelt sich hierbei um einen Schalter, der entweder vorhanden ist oder nicht. Dieser Kommandozeilenparameter kann auf keinen Wert gesetzt werden. Er wird später im Beispielprogramm verwendet, um unbekannte und nicht in desc definierte Kommandozeilenparameter anzuzeigen.

Während im Beispiel 63.1 die Funktion boost::program_options::parse_command_line() verwendet wurde, um Kommandozeilenparameter zu parsen, wird im Beispiel 63.2 direkt auf einen Parser vom Typ boost::program_options::command_line_parser zugegriffen. Dem Konstruktor wird über argc und argv die Kommandozeile übergeben.

boost::program_options::command_line_parser bietet mehrere Methoden an. Sie müssen auf alle Fälle options() aufrufen, weil Sie mit dieser Methode dem Parser die Definitionen der Kommandozeilenparameter übergeben.

options() gibt wie auch alle anderen Methoden der Klasse eine Referenz auf den gleichen Parser zurück. So können mehrere Methodenaufrufe direkt nacheinander erfolgen. Im Beispiel 63.2 wird nach options() die Methode allow_unregistered() aufgerufen, um dem Parser mitzuteilen, dass er bei unbekannten Kommandozeilenparametern keine Ausnahme werfen soll. Über style() wird angegeben, dass für Kommandozeilenparameter in der Kurzversion auch der Schrägstrich verwendet werden darf. Sie können also die Hilfe nicht nur mit -h, sondern auch mit /h aufrufen.

Beachten Sie, dass boost::program_options::parse_command_line() einen vierten Parameter unterstützt, der an style() weitergereicht wird. Sie können diese Hilfsfunktion aufrufen und müssen nicht explizit auf den Parser zugreifen, wenn Sie eine Option wie boost::program_options::command_line_style::allow_slash_for_short verwenden möchten.

Nachdem Sie die gewünschten Einstellungen vorgenommen haben, rufen Sie für den Parser run() auf. Diese Methode gibt die geparsten Kommandozeilenparameter in einem Objekt vom Typ boost::program_options::parsed_options zurück, das Sie an boost::program_options::store() weiterreichen, um die Kommandozeilenparameter in vm zu speichern.

Im unteren Teil von Beispiel 63.2 wird wie zuvor auf vm zugegriffen, um Kommandozeilenparameter auszuwerten. Neu ist lediglich die Funktion boost::program_options::collect_unrecognized(), die für den Kommandozeilenparameter --unreg aufgerufen wird. Dieser Funktion muss das Objekt vom Typ boost::program_options::parsed_options übergeben werden, das von run() zurückgegeben wurde. Die Funktion gibt daraufhin alle unbekannten Kommandozeilenparameter in einem std::vector<std::string> zurück. Wenn Sie das Programm zum Beispiel mit test --unreg --abc aufrufen, wird --abc ausgegeben.

Beachten Sie, dass boost::program_options::collect_unrecognized() als zweiter Parameter boost::program_options::exclude_positional übergeben wird. Damit geben Sie an, dass Positionsparameter ignoriert werden sollen. Im Beispiel 63.2 spielt dies keine Rolle, weil keine Positionsparameter definiert sind. boost::program_options::collect_unrecognized() ist jedoch so definiert, dass Sie einen entsprechenden Parameter angeben müssen.

Was Positionsparameter sind, soll Ihnen anhand folgenden Beispiels gezeigt werden.

Beispiel 63.3. Positionsparameter bei Boost.ProgramOptions
#include <boost/program_options.hpp>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>

using namespace boost::program_options;

void to_cout(const std::vector<std::string> &v)
{
  std::copy(v.begin(), v.end(),
    std::ostream_iterator<std::string>{std::cout, "\n"});
}

int main(int argc, const char *argv[])
{
  try
  {
    options_description desc{"Options"};
    desc.add_options()
      ("help,h", "Help screen")
      ("phone", value<std::vector<std::string>>()->
        multitoken()->zero_tokens()->composing(), "Phone");

    positional_options_description pos_desc;
    pos_desc.add("phone", -1);

    command_line_parser parser{argc, argv};
    parser.options(desc).positional(pos_desc).allow_unregistered();
    parsed_options parsed_options = parser.run();

    variables_map vm;
    store(parsed_options, vm);
    notify(vm);

    if (vm.count("help"))
      std::cout << desc << '\n';
    else if (vm.count("phone"))
      to_cout(vm["phone"].as<std::vector<std::string>>());
  }
  catch (const error &ex)
  {
    std::cerr << ex.what() << '\n';
  }
}

Beispiel 63.3 definiert --phone als Positionsparameter. Dazu wird auf die Klasse boost::program_options::positional_options_description zugegriffen. Diese Klasse bietet eine Methode add() an, der der Name eines Positionsparameters und eine Positionsnummer übergeben werden muss. Im Beispiel wird phone und -1 übergeben.

Die Idee hinter Positionsparametern ist, dass Sie Werte auf der Kommandozeile angeben können, ohne sie hinter einen Kommandozeilenparameter stellen zu müssen. So können Sie Beispiel 63.3 wie folgt aufrufen:

test 123 456

Obwohl --phone nicht verwendet wird, werden 123 und 456 als Telefonnummern erkannt.

Wenn Sie die Methode add() für ein Objekt vom Typ boost::program_options::positional_options_description aufrufen, weisen Sie Angaben auf der Kommandozeile über die Positionsnummer einem Kommandozeilenparameter zu. Im obigen Fall hat 123 die Positionsnummer 0 und 456 die Positionsnummer 1. Da im Beispiel 63.3 -1 an add() übergeben wurde, werden alle Angaben --phone zugewiesen. Würde zum Beispiel 0 übergeben werden, würde ausschließlich 123 als Telefonnummer erkannt werden. Würde 1 übergeben werden, würde lediglich 456 als Telefonnummer geparst werden.

Beachten Sie, dass pos_desc mit positional() an den Parser übergeben wird. Nur dann weiß der Parser, welche Kommandozeilenparameter Positionsparameter sind.

Beachten Sie, dass Sie außerdem sicherstellen müssen, dass die von Ihnen verwendeten Positionsparameter definiert sind. Für Beispiel 63.3 bedeutet dies, dass phone nur deswegen an add() übergeben werden kann, weil es eine Definition für den Kommandozeilenparameter --phone in desc gibt.

In allen Beispielen wurde Boost.ProgramOptions bisher verwendet, um Kommandozeilenparameter zu parsern. Die Bibliothek unterstützt jedoch auch das Laden von Parametern aus einer Datei. Speziell für den Fall, dass wiederholt die gleichen Kommandozeilenparameter angegeben werden müssen, kann eine Konfigurationsdatei hilfreich sein.

Beispiel 63.4. Parameter von einer Konfigurationsdatei laden
#include <boost/program_options.hpp>
#include <string>
#include <fstream>
#include <iostream>

using namespace boost::program_options;

int main(int argc, const char *argv[])
{
  try
  {
    options_description generalOptions{"General"};
    generalOptions.add_options()
      ("help,h", "Help screen")
      ("config", value<std::string>(), "Config file");

    options_description fileOptions{"File"};
    fileOptions.add_options()
      ("age", value<int>(), "Age");

    variables_map vm;
    store(parse_command_line(argc, argv, generalOptions), vm);
    if (vm.count("config"))
    {
      std::ifstream ifs{vm["config"].as<std::string>().c_str()};
      if (ifs)
        store(parse_config_file(ifs, fileOptions), vm);
    }
    notify(vm);

    if (vm.count("help"))
      std::cout << generalOptions << '\n';
    else if (vm.count("age"))
      std::cout << "Your age is: " << vm["age"].as<int>() << '\n';
  }
  catch (const error &ex)
  {
    std::cerr << ex.what() << '\n';
  }
}

Beispiel 63.4 verwendet zwei Objekte vom Typ boost::program_options::options_description. generalOptions definiert die Parameter, die auf der Kommandozeile angegeben werden können. fileOptions legt einen Parameter fest, der von einer Konfigurationsdatei geladen werden kann.

Sie müssen Ihre Parameter nicht zwingend in unterschiedlichen Objekten vom Typ boost::program_options::options_description definieren. Eine Aufteilung bietet sich für Beispiel 63.4 insofern an, um zu verhindern, dass --help in die Konfigurationsdatei gesetzt wird. Das Programm würde schließlich bei jedem Start die Hilfe anzeigen.

Im Beispiel 63.4 soll --age von einer Konfigurationsdatei geladen werden können. Der Name der Konfigurationsdatei soll dabei als Kommandozeilenparameter an das Programm übergeben werden können. Dazu wurde --config in generalOptions definiert.

Nachdem die Kommandozeilenparameter mit boost::program_options::parse_command_line() geparst und in vm gespeichert wurden, wird überprüft, ob --config gesetzt ist. Ist dies der Fall, wird die Konfigurationsdatei mit std::ifstream geöffnet. Das entsprechende Objekt vom Typ std::ifstream wird an eine Funktion boost::program_options::parse_config_file() übergeben. Außerdem wird fileOptions übergeben, das die Parameter beschreibt. boost::program_options::parse_config_file() macht das Gleiche wie boost::program_options::parse_command_line() und gibt die geparsten Parameter als ein Objekt vom Typ boost::program_options::parsed_options zurück. Dieses wird an boost::program_options::store() übergeben, um die geparsten Parameter in vm abzulegen.

Sie können das Programm testen, wenn Sie eine Konfigurationsdatei erstellen und in ihr zum Beispiel age=29 speichern. Heißt die Konfigurationsdatei config.txt, rufen Sie das Programm wie folgt auf:

test --config config.txt

Sie erhalten daraufhin die Ausgabe:

Your age is: 29

Wenn Sie die gleichen Parameter sowohl auf der Kommandozeile als auch in einer Konfigurationsdatei unterstützen, weil Sie zum Beispiel nur ein Objekt vom Typ boost::program_options::options_description verwenden, kann es sein, dass Ihr Programm zweimal den gleichen Parameter parst – einmal mit boost::program_options::parse_command_line() und einmal mit boost::program_options::parse_config_file(). Es hängt von der Reihenfolge der Funktionsaufrufe ab, welchen Parameterwert Sie in vm vorfinden. Wenn einmal ein Parameterwert in vm gespeichert ist, wird er nicht überschrieben. Das heißt, die erste Angabe gilt. Ob diese Angabe von der Kommandozeile oder aus einer Konfigurationsdatei stammt, legen Sie über die Reihenfolge der Funktionsaufrufe fest.

Neben boost::program_options::parse_command_line() und boost::program_options::parse_config_file() bietet Boost.ProgramOptions auch eine Funktion boost::program_options::parse_environment() an, um Parameter von Umgebungsvariablen zu laden. Die Bibliothek stellt auch eine Klasse boost::environment_iterator zur Verfügung, mit der über Umgebungsvariablen iteriert werden kann.