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:
PackageOptions
constructor (first call is in LibraryOptions
constructor.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.
Do I need to default some constructors?
Only if you need default constructed instances. The compiler will tell you when this was required.
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.
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!
Result of my review pass (I do active reading to see what is going on):
#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.