This is relatively easy if I understood you correctly, using what's called a factory-pattern.
If you can list all of the derived types:
Letter header:
struct Letter {
enum LetterEnum {LetterA, LetterB, LetterC, LetterCount};
virtual ~Letter() {} //base types should always have virtual destructor
virtual void foo() = 0;
static std::unique_ptr<Letter> construct(LetterEnum c);
};
Implementation headers:
struct A : Letter {
void foo() override;
};
struct B : Letter {
void foo() override;
};
struct C : Letter {
void foo() override;
};
Letter body:
std::unique_ptr<Letter> Letter::construct(Letter::LetterEnum c)
{
switch(c) {
case Letter::LetterA : return make_unique<A>();
case Letter::LetterB : return make_unique<B>();
case Letter::LetterC : return make_unique<C>();
default: throw ...;
}
}
Usage:
int main() {
char c;
std::cin >> c;
//get a letter of the derived type associated with the letter entered
std::unique_ptr<Letter> ptr = Letter::construct(c);
}
If you can't list all of the derived types:
Allow the derived types to register themselves with the Letter
class, and then Letter
can use that to create each of the derived types. This way, adding and deleting the derived types involves no changes to any other files. Easy!
struct Letter {
virtual ~Letter() {} //destructor is always virtual when inheretence is involved
....
//this is a "shared" function in the Letter class itself
//it takes a letter, and returns a dynamically allocated instance
//of the derived type corresponding with that letter
static std::unique_ptr<Letter> construct(char c);
//this typedef represents the actual function that returns
//each dynamically allocated derived type
typedef std::function<std::unique_ptr<Letter>()> letter_ctor;
//this is a "shared" function in the Letter class itself
//it takes a letter, and a function that creates derived types,
//and saves them inside the container ctors
static bool register(char c, letter_ctor func);
private:
//this is a "shared" member in the Letter class.
//There is only one shared by all of the Letters. Like a global.
//When you give it a letter, it gives you a function.
//and is VERY fast for large numbers of entries
static std::unordered_set<char,letter_ctor> ctors;
};
and in your implementation file:
//here's the function that derived types register themselves with
//pretty straightforward, just inserts the pair into the unordered_map
bool Letter::register(char c, Letter::letter_ctor func)
{return Letter::ctors.insert(std::make_pair(c,std::move(func))).second;}
//and here's the function that creates the derived types
//it checks if the letter is in the unordered_map
//if the letter isn't there, it throws an exception
//otherwise, it calls the function associated with that letter
//which creates the derived type on the heap, and returns a pointer to it
std::unique_ptr<Letter> Letter::construct(char c)
{
auto it = Letter::ctors.find(c);
if (it == Letter::ctors.end())
throw ...;
return it->second(); //construct that letter
}
and then your derived types do this:
//you know this part
struct LetterA : public Letter
{
....
};
//derived types have to register themselves:
//this is a global, so when the program loads, it automatically calls this
//even before main runs*
//it registers the letter 'A' and a function that creates a LetterA class on the heap
static bool registerA = Letter::register('A', [](){return make_unique<LetterA>();});
and then you can easily create arbirary derived types!
int main() {
char c;
std::cin >> c;
//get a letter of the derived type associated with the letter entered
std::unique_ptr<Letter> ptr = Letter::construct(c);
}
*It doesn't always get called before main. If you have problems, put an bool init_A();
in the A header, and bool init_A(){return true;}
in the A implementation file, and in your main file have static bool AInit=init_A();
which should force it. This is almost never needed in practice though.
As a side note, these depend on having a make_unique
, which should have been in C++11, but was left out due to oversight. It will be in C++14. In the meantime, use this:
template<class T, class...Us>
std::unique_ptr<T> make_unique(Us&&...us)
{return std::unique_ptr<T>(new T(std::forward<Us>(us)...));}