3

Let's suppose I have some C++ abstract class and all its inherited classes have different constructors:

class Abstract{
//don't worry, there is some pure virtual method here
}

class A : public Abstract {
public:
  A (int Afirst, std::string Asecond, int Athird) {...}
...
} 

class B : public Abstract {
public
  B (double Bfirst, std::int Bsecond) {...}
...
}

class C : public Abstract {
public 
  C (std::string Cfirst, double Csecond, int Cthird, float Cfourth) {...}
}

As you can see, all the inherited class have (possibly) different constructors.

Now, I want to write a generic main(), something like:

int main (int argc, char *argv[]){
  if(argc < 2){
    std::cerr<<"Too few arguments!"<<std::endl;
    exit(1);
  }
  std::string type = argv[1];
  Abstract *abs;
  if(!type.compare("A"){
    if(argc < 5){
      std::cerr<<"Too few arguments for A!"<<std::endl;
      exit(1);
    }
    abs = new A(atoi(argv[2]), argv[3], argv[4]); 
  }
  //similar for B, C, D
} 

I wonder if there is a best way to do this, for example passing directly char *argv[] to each constructor and make all the checks inside the constructor (and eventually throwing an exception as described here).

Community
  • 1
  • 1
justHelloWorld
  • 6,478
  • 8
  • 58
  • 138
  • 3
    You won't be able to get out of having to check if the arguments match a given constructor imho, but I would not pollute the classes with the checking code. I'd write a factory function that takes `argc` and `argv`and returns an instance of the appropriate class. That would keep the checking code contained. – Unimportant Dec 31 '16 at 11:42

1 Answers1

2

You may do something like that to be generic:

// functions to convert const char* to given type
template <typename T> T To(const char*);

template <> int To(const char* s) { return atoi(s); }
template <> const char* To(const char* s) { return s; }
template <> std::string To(const char* s) { return s; }
// ...

// Your classes:
struct Abstract { virtual ~Abstract() = default; };

struct A : Abstract { A (int, std::string, int) {}};
struct B : Abstract { B (int, int) {}};
// ...

namespace detail
{    
    // Helper functions for the factory.
    template <typename T, typename Tuple, std::size_t... Is>
    std::unique_ptr<Abstract> make_abstract(const char*argv[], std::index_sequence<Is...>)
    {
        return std::make_unique<T>(To<std::tuple_element_t<Is, Tuple>>(argv[2 + Is])...);
    }

    template <typename T, typename Tuple>
    std::unique_ptr<Abstract> make_abstract(int argc, const char*argv[])
    {
        constexpr int tuple_size = std::tuple_size<Tuple>::value;
        if (argc < tuple_size) {
            throw std::runtime_error("Too few arguments");   
        }
        return make_abstract<T, Tuple>(argv, std::make_index_sequence<tuple_size>());
    }
}

// The public factory
std::unique_ptr<Abstract> make_abstract(int argc, const char*argv[])
{
    if (argc == 1) {
        return nullptr;
    }
    const std::string name = argv[1];
    if (name == "A") {
        return detail::make_abstract<A, std::tuple<int, std::string, int>>(argc, argv);
    } else if (name == "B") {
        return detail::make_abstract<B, std::tuple<int, int>>(argc, argv);
    }
    // ...
    return nullptr;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302