英文:
Create artificial argv array for legacy parent class accepting main-like arguments in the constructor
问题
I am struggling with the legacy code (refactoring scheduled, but I need to use it now) that has the devicedrivers designed to accept (argc, argv)
arguments for its constructors. The base class that I have to use, and for now I cannot change it, looks like this:
class LegacyDriver
{
public:
LegacyDriver(int argc, char** argv)
{
if (argc < 3)
{
throw std::runtime_error("nope");
}
// some logic with the arguments interpretation
int var1 = std::stoi(argv[1]);
int var2 = std::stoi(argv[2]);
std::cout << "config: " << var1 << ", " << var2 << "\n";
// etc ...
}
virtual ~LegacyDriver() = default;
// + some public interfaces, not important ...
};
Then my new driver that has to extend this class, needs to somehow pass that argc, argv
args down to the base class. So far my colleagues were just playing along:
class MyNewDriver: public LegacyDriver
{
public:
MyNewDriver(int argc, char** argv): LegacyDriver(argc, argv)
{}
~MyNewDriver() override = default;
};
but this design is clearly not sustainable (e.g. difficult for testing) and I would like to at least provide a meaningful constructor to the new driver, like:
MyNewDriver(int param1, int param2): LegacyDriver(somehowTransformThemIntoArgcArgv)
{}
I can't use additional MyNewDriver
private fields for that because the parent class has to be constructed before. I thought about a hack like this, with the strings and vector in the global scope:
namespace {
std::string hackyProgramName = "Driver";
std::string hackyVar1;
std::string hackyVar2;
std::vector<const char*> hackyArgv;
auto constructArgv = [](int var1, int var2)
{
hackyVar1 = std::to_string(var1);
hackyVar2 = std::to_string(var2);
hackyArgv = std::vector<const char*>({hackyProgramName.c_str(), hackyVar1.c_str(), hackyVar2.c_str()});
return const_cast<char**>(hackyArgv.data());
};
}
class MyNewDriver: public LegacyDriver
{
public:
MyNewDriver(int argc, char** argv): LegacyDriver(argc, argv)
{}
MyNewDriver(int param1, int param2): LegacyDriver(3, constructArgv(param1, param2))
{}
~MyNewDriver() override = default;
};
But it just looks so ugly, and argv
vector is invalidated with every new instance of my driver.
Is there a better way to do this? Preferably with a single lambda.
英文:
I am struggling with the legacy code (refactoring scheduled, but I need to use it now) that has the devicedrivers designed to accept (argc, argv)
arguments for its constructors. The base class that I have to use, and for now I cannot change it, looks like this:
class LegacyDriver
{
public:
LegacyDriver(int argc, char** argv)
{
if (argc < 3)
{
throw std::runtime_error("nope");
}
// some logic with the arguments interpretation
int var1 = std::stoi(argv[1]);
int var2 = std::stoi(argv[2]);
std::cout << "config: " << var1 << ", " << var2 << "\n";
// etc ...
}
virtual ~LegacyDriver() = default;
// + some public interfaces, not important ...
};
Then my new driver that has to extend this class, needs to somehow pass that argc, argv
args down to the base class. So far my colleagues were just playing along:
class MyNewDriver: public LegacyDriver
{
public:
MyNewDriver(int argc, char** argv): LegacyDriver(argc, argv)
{}
~MyNewDriver() override = default;
};
but this design is clearly not sustainable (e.g. difficult for testing) and I would like to at least provide a meaningful constructor to the new driver, like:
MyNewDriver(int param1, int param2): LegacyDriver(somehowTransformThemIntoArgcArgv)
{}
I can't use additional MyNewDriver
private fields for that, because parent class has to be constructed before.
I thought about the hack like this, with the strings and vector in the global scope:
namespace {
std::string hackyProgramName = "Driver";
std::string hackyVar1;
std::string hackyVar2;
std::vector<const char*> hackyArgv;
auto constructArgv = [](int var1, int var2)
{
hackyVar1 = std::to_string(var1);
hackyVar2 = std::to_string(var2);
hackyArgv = std::vector<const char*>({hackyProgramName.c_str(), hackyVar1.c_str(), hackyVar2.c_str()});
return const_cast<char**>(hackyArgv.data());
};
}
class MyNewDriver: public LegacyDriver
{
public:
MyNewDriver(int argc, char** argv): LegacyDriver(argc, argv)
{}
MyNewDriver(int param1, int param2): LegacyDriver(3, constructArgv(param1, param2))
{}
~MyNewDriver() override = default;
};
But it just looks so ugly, and argv
vector is invalidated with every new instance of my driver.
Is there a better way to do this? Preferably with single lambda.
答案1
得分: 5
因为您必须维护一个有效的 argv
数组,并且在构造 LegacyDriver
之前执行操作,您可以继承一个类之前。
class PreDriver {
protected:
PreDriver(int var1, int var2) {
hackyVar1 = std::to_string(var1);
hackyVar2 = std::to_string(var2);
hackyArgv = std::vector<const char*>{"Driver", hackyVar1.c_str(), hackyVar2.c_str()};
};
constexpr int argc() const { return 3; }
char** argv() { return const_cast<char**>(hackyArgv.data()); }
private:
std::string hackyVar1;
std::string hackyVar2;
std::vector<const char*> hackyArgv;
};
class MyNewDriver : private PreDriver, public LegacyDriver {
public:
MyNewDriver(int param1, int param2) : PreDriver(param1, param2), LegacyDriver(this->PreDriver::argc(), this->PreDriver::argv()) {}
~MyNewDriver() override = default;
};
英文:
Because you must maintain a valid argv
array and the action is to do before constuction of LegacyDriver
, you can inherit of a class before.
class PreDriver {
protected:
PreDriver( int var1, int var2 ) {
hackyVar1 = std::to_string( var1 );
hackyVar2 = std::to_string( var2 );
hackyArgv = std::vector<const char*>{"Driver", hackyVar1.c_str(), hackyVar2.c_str()};
};
constexpr int argc()const { return 3; }
char** argv() { return const_cast<char**>(hackyArgv.data()); }
private:
std::string hackyVar1;
std::string hackyVar2;
std::vector<const char*> hackyArgv;
};
class MyNewDriver : private PreDriver, public LegacyDriver {
public:
MyNewDriver( int param1, int param2 ) : PreDriver(param1,param2), LegacyDriver( this->PreDriver::argc(), this->PreDriver::argv() ) {}
~MyNewDriver() override = default;
};
答案2
得分: 4
当你移动一个向量时,指向它的指针仍然有效。因此,你可以首先构造 argv 向量,然后将它移动到一个成员中:
class ArgsHolder {
std::vector<std::string> string_storage;
std::vector<char*> argv_storage;
public:
/*explicit(false)*/ ArgsHolder(std::vector<std::string> v) : string_storage(std::move(v)), argv_storage(string_storage.size() + 1) {
// 请记住为 argv[argc] 的 nullptr 做了 +1
for (std::size_t i = 0; i < string_storage.size(); ++i)
argv_storage[i] = string_storage[i].data();
}
char** argv() noexcept { return argv_storage.data(); }
int argc() noexcept { return argv_storage.size() - 1u; }
};
class MyNewDriver: public LegacyDriver
{
ArgsHolder args;
public:
MyNewDriver(int argc, char** argv): LegacyDriver(argc, argv)
{}
MyNewDriver(ArgsHolder args): LegacyDriver(args.argc(), args.argv()), args(std::move(args)) {}
MyNewDriver(int param1, int param2)
: MyNewDriver(ArgsHolder({ "Driver", std::to_string(param1), std::to_string(param2) }))
{}
~MyNewDriver() override = default;
};
英文:
When you move a vector, pointers into it are still valid. So you can construct the argv vector first then move it to a member:
class ArgsHolder {
std::vector<std::string> string_storage;
std::vector<char*> argv_storage;
public:
/*explicit(false)*/ ArgsHolder(std::vector<std::string> v) : string_storage(std::move(v)), argv_storage(string_storage.size() + 1) {
// Remember the +1 for the nullptr at argv[argc]
for (std::size_t i = 0; i < string_storage.size(); ++i)
argv_storage[i] = string_storage[i].data();
}
char** argv() noexcept { return argv_storage.data(); }
int argc() noexcept { return argv_storage.size() - 1u; }
};
class MyNewDriver: public LegacyDriver
{
ArgsHolder args;
public:
MyNewDriver(int argc, char** argv): LegacyDriver(argc, argv)
{}
MyNewDriver(ArgsHolder args): LegacyDriver(args.argc(), args.argv()), args(std::move(args)) {}
MyNewDriver(int param1, int param2)
: MyNewDriver(ArgsHolder({ "Driver", std::to_string(param1), std::to_string(param2) }))
{}
~MyNewDriver() override = default;
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论