Create artificial argv array for legacy parent class accepting main-like arguments in the constructor

huangapple go评论67阅读模式
英文:

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 &lt; 3)
    {
      throw std::runtime_error(&quot;nope&quot;);
    }
    // some logic with the arguments interpretation
    int var1 = std::stoi(argv[1]);
    int var2 = std::stoi(argv[2]);
    std::cout &lt;&lt; &quot;config: &quot; &lt;&lt; var1 &lt;&lt; &quot;, &quot; &lt;&lt; var2 &lt;&lt; &quot;\n&quot;;
    // 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 = &quot;Driver&quot;;
std::string hackyVar1;
std::string hackyVar2;
std::vector&lt;const char*&gt; hackyArgv;
auto constructArgv = [](int var1, int var2)
{
  hackyVar1 = std::to_string(var1);
  hackyVar2 = std::to_string(var2);
  hackyArgv = std::vector&lt;const char*&gt;({hackyProgramName.c_str(), hackyVar1.c_str(), hackyVar2.c_str()});
  return const_cast&lt;char**&gt;(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&lt;const char*&gt;{&quot;Driver&quot;, hackyVar1.c_str(), hackyVar2.c_str()};
    };
    constexpr int  argc()const     { return 3; }
    char**  argv() { return const_cast&lt;char**&gt;(hackyArgv.data()); }
private:
    std::string hackyVar1;
    std::string hackyVar2;
    std::vector&lt;const char*&gt;  hackyArgv;
};

class MyNewDriver : private PreDriver, public LegacyDriver {
public:
    MyNewDriver( int param1, int param2 ) : PreDriver(param1,param2), LegacyDriver( this-&gt;PreDriver::argc(), this-&gt;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&lt;std::string&gt; string_storage;
    std::vector&lt;char*&gt; argv_storage;
public:
    /*explicit(false)*/ ArgsHolder(std::vector&lt;std::string&gt; 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 &lt; 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({ &quot;Driver&quot;, std::to_string(param1), std::to_string(param2) }))
  {}
  ~MyNewDriver() override = default;
};

huangapple
  • 本文由 发表于 2023年6月29日 20:55:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/76581274.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定