简体   繁体   中英

How to implement polymorphic boost program options class?

I am relatively new to object inheritance, I am aware of rule of 0/3/5, but I have always avoided deriving classes and mainly wrote codes applying rule of 0.

I have a large package which seems to have memory leak with a class from the library it uses that handles program options via boost library. Someone declared boost::program_options::options_description as pointer and my guess is that something is off with the destructor destroying the raw pointer once this class is used in multiple derivations and slicing. I almost never use pointers apart from std::shared_ptr and std::unique_ptr, so I have decided to reimplement the entire class from scratch.

The base class is:

class OptionsBase
{
   protected:
        boost::program_options::options_description program_specific_options;
        void add_prog_specific_options_and_parse(int argc, char* argv[]);
   public:
        boost::program_options::variables_map   vm;
        OptionsBase(int argc, char* argv[]);
}

Defined as:

 OptionsBase::OptionsBase() : 
    program_specific_options(boost::program_options::options_description("Program-specific options")),
    vm() 
    {}

void OptionsBase::add_prog_specific_options_and_parse(int argc, char* argv[])
{
   try 
   {  boost::program_options::store(boost::program_options::command_line_parser(argc,argv).options(program_specific_options).allow_unregistered().run(), vm);
      boost::program_options::notify(vm);
   }
   catch(std::exception& e)
   {
      std::cerr << e.what() << std::endl;
      exit(EXIT_FAILURE);
   }
}

This is used in the Library in the following way:

class LibraryOptions : public OptionsBase
{
   const double get_something()     const {return vm["something"].as<double>();};
   LibraryOptions(int argc, char* argv[])
}

Definition:

LibraryOptions::LibraryOptions(int argc, char* argv[])
: BaseOptions()
{
   program_specific_options.add_options()
   ("something", boost::program_options::value<double>()->default_value(0.0), "some random program option");
    add_prog_specific_options_and_parse(argc, argv);

}

In the package that uses the Library, I wish to derive another class from LibraryOptions :

  class PackageOptions : public LibraryOptions
  {
     const double get_another_option() const {return vm["anotheroption"].as<double>();};
     PackageOptions(int argc, char* argv[])
  }

Definition:

PackageOptions::PackageOptions(int argc, char* argv[])
: LibraryOptions(argc,argv)
{
   program_specific_options.add_options()
   ("anotheroption", boost::program_options::value<double>()->default_value(5.0), "Extra options");
    add_prog_specific_options_and_parse(argc, argv);
}

Finally, I want to use this in the following way:

int main(int argc, char* argv [])
{
   PackageOptions opt(argc,argv);
   ComplexObject cob(opt);
   double a=cob.do_something();
}

where ComplexObject and do_something are:

class ComplexObject
{
   PackageOptions opt;
   double do_something() const
   {
      AnotherComplexObject calculator(opt);
      return calculator.do_something_else() * opt.get_another_option();
   };
   ComplexObject(const PackageOptions& _opt) : opt(_opt) {};
}

class AnotherComplexObject
{
   LibraryOptions opt;
   double do_something_else()
   {
       double b = opt.get_something(); // imagine really complex code that needs options from opt object
       return b;
   }
   AnotherComplexObject(const LibraryOptions& _opt) : opt(_opt) {};
}

So child PackageOptions needs to be upcasted to its parent LibraryOptions because ComplexObject's member function is creating AnotherComplexObject that is written to deal with LibraryOptions object.

I know this above seems redundant, as clearly one options class with all the options would resolve all the confusion. The variable map of the base class already ignores unregistered options, so this implementation seems overcomplicated. However, the real idea throughout my package is that LibraryOptions belongs to the library package and I am building a new package that uses it. Obviously, the new package has some additional options to the ones given by LibraryOptions object, which is why PackageOptions object makes sense. I think that sending PackageOptions object when LibraryOptions object is needed should automatically upcast the child into parent and slice it, which is ok. I am mainly worried are these object correctly copying and moving with rule of 0 enforced above.

Overall, my questions are:

  • Am I doing something fundamentally wrong? I noticed that I am calling add_prog_specific_options_and_parse(argc,argv) twice in PackageOptions constructor (first call is in LibraryOptions constructor.
  • Do I need to default some constructors?
  • Do I need virtual destructor in OptionsBase class?
  • Would this work properly if I redesign my code to pass std::shared_ptr<PackageOptions> around? I think I am wasting some time coping PackageOptions and LibraryOptions objects in objects as ComplexObject . Object ComplexObject does not need to own the field PackageOptions opt , these options are mainly objects filled with convenient get_methods that access the variable map in the options class.

The code seems to be working and memory leak has been removed, I am just unsure if I implemented the code correctly.

  1. Do I need to default some constructors?

    Only if you need default constructed instances. The compiler will tell you when this was required.

  2. Do I need virtual destructor in OptionsBase class?

    These are required in virtual types ( and ) when you could be deleting through a pointer-to-base instead of the derived type.

    Yours types aren't virtual:

     #include <type_traits> static_assert(not(std::is_polymorphic_v<OptionsBase> || std::is_polymorphic_v<LibraryOptions> || std::is_polymorphic_v<PackageOptions>));

    (I did some renames to understand the hierarchy more clearly, see full listing below)

    Also, they're not dynamically allocated.

  3. Would this work properly if I redesign my code to pass std::shared_ptr around?

    Yes. To avoid any potential trouble make it std::shared_ptr<PackageOptions const> and live a happy life!

Listing

Result of my review pass (I do active reading to see what is going on):

Live On Coliru

#include <boost/program_options.hpp>
#include <iostream>
namespace po = boost:: program_options;

class OptionsBase {
  protected:
    po::options_description program_specific_options;

    void add_prog_specific_options_and_parse(int argc, char* argv[]) {
        try {
            store(po::command_line_parser(argc, argv)
                                             .options(program_specific_options)
                                             .allow_unregistered()
                                             .run(),
                                         vm);
            notify(vm);
        } catch (std::exception const& e) {
            std::cerr << "add_prog_specific_options_and_pars: " << e.what() << std::endl;
            ::exit(EXIT_FAILURE);
        }
    }

  public:
    po::variables_map vm;
    OptionsBase()
        : program_specific_options(po::options_description("Program-specific options"))
        , vm() {}

    OptionsBase(int argc, char* argv[]);
};

class LibraryOptions : public OptionsBase {
  public:
    double get_something() const { return vm["libx"].as<double>(); };
    LibraryOptions(int argc, char* argv[]) : OptionsBase() {
        program_specific_options.add_options()                                              //
            ("libx", po::value<double>()->default_value(0.0), "some random program option") //
            ;
        add_prog_specific_options_and_parse(argc, argv);
    }
};

class PackageOptions : public LibraryOptions {
  public:
    double get_another_option() const { return vm["pkgx"].as<double>(); };
    PackageOptions(int argc, char* argv[]) : LibraryOptions(argc, argv) {
        program_specific_options.add_options()                                 //
            ("pkgx", po::value<double>()->default_value(5.0), "Extra options") //
            ;
        add_prog_specific_options_and_parse(argc, argv);
    }
};

class LibraryObject {
    LibraryOptions opt;

  public:
    double do_something_else() { return opt.get_something(); }
    LibraryObject(LibraryOptions const& _opt) : opt(_opt){};
};

class PackageObject {
    PackageOptions opt;

  public:
    double do_something() const {
        LibraryObject calculator(opt);
        return calculator.do_something_else() * opt.get_another_option();
    };
    PackageObject(PackageOptions const& _opt) : opt(_opt){};
};

#include <type_traits>
static_assert(not(std::is_polymorphic_v<OptionsBase> || std::is_polymorphic_v<LibraryOptions> ||
                  std::is_polymorphic_v<PackageOptions>));

#include <boost/range.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <functional>
using boost::adaptors::transformed;
using namespace std::string_literals;

int main() {
    auto args = std::array{"programx"s, "--libx"s, "42"s, "--pkgx"s, "2"s};
    auto argv = copy_range<std::vector<char*>>(args | transformed([](auto& s) { return s.data(); }));
    argv.push_back(nullptr);

    PackageOptions opt(args.size(), argv.data());
    PackageObject  pob(opt);

    std::cout << pob.do_something() << "\n";
}

Prints

84

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM