2

In C++ is there a way to pass a type (e.g. a class) as parameter to a function?

Explanation why I need this: There is a abstract data class and a manager class. The manager class contains a list (or in this case a map) of objects of derived classes of data. I use unique_ptr for this as mentioned in the answer of this question.

class Data{}; // abstract

class Manager
{
    map<string, unique_ptr<Data>> List;
};

Now imagine I want to add a new data storage to the manager.

class Position : public Data
{
    int X;
    int Y;
}

How could I tell the manager to create a object of that type and refer an unique_ptr to it?

Manager manager;
manager.Add("position data", Position);

In this case I would need to pass the class Position to the add function of the manager class since I don't want to have to first create an instance and then send it to the manager.

And then, how could I add the object of that class to the List?

I am not sure if there is a way of doing that in C++. If that can't be done easily I would really like to see a workaround. Thanks a lot!

Community
  • 1
  • 1
danijar
  • 32,406
  • 45
  • 166
  • 297

4 Answers4

5

You can use templates. In each type deriving from Data you will have to define a 'creator' function, which have the following prototype: Derived* create(). It will be called internally (you can also return a unique_ptr, but that would requires more memory).

Ex:

struct Position: public Data
{
    // ...
    static Position* create()
    {
        return new Position();
    }
};

The Add method will be:

template<typename D>
void Add(String str)
{
    List.insert(std::make_pair(str, std::unique_ptr<Data>(D::create())));
}

Then you use it like this:

Manager manager;
manager.Add<Position>("position data");

EDIT

You can also get rid of the create functions, by using this Add method:

template<typename D>
void Add(String str)
{
    List.insert(std::make_pair(str, std::unique_ptr<Data>(new D())));
}

Advantage: less code in data structure code.

Inconvenient: data structures have less control on how they're built.

Synxis
  • 9,236
  • 2
  • 42
  • 64
  • That is a nice solution, too. But is there a way of getting rid of that `create` function? (Maybe move it to the base class somehow?) – danijar Oct 28 '12 at 14:19
  • Yes, you can directly use `new D()`. Less control from the structure, but more easy to implement. See edit. – Synxis Oct 28 '12 at 14:23
  • Looks nice, but there is a compiler error. `Binary Operator '=': There was no operator found which accepts an operand of type 'Position *'.` – danijar Oct 28 '12 at 14:29
  • 1
    Yes that was code only to show what you should do 'self-documenting code). Editing this. – Synxis Oct 28 '12 at 14:30
  • Thanks that works now. (By the way you missed one `)` in both lines.) – danijar Oct 28 '12 at 14:34
3

What about something like this:

class Manager
{
 public:
  template <typename T>
  void addData(const std::string& title)
  {
    List.insert(std::make_pair(title, std::unique_ptr<Data>(new T));
  }
 private:   
  map<string, unique_ptr<Data>> List;
};

then

Manager manager;
manager.addData<Position>("position data");
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • This is nice. It there any way to restrict `typename T` to be derived from `Data`? – danijar Oct 28 '12 at 14:16
  • 2
    @sharethis well, if `T` wasn't derived from `Data`, `std::unique_ptr(new T)` would give compilation problems, unless there was a conversion from `T` to `Data`. – juanchopanza Oct 28 '12 at 14:20
  • 1
    @sharethis if you really want to make sure, and you have C++11 support, you can use [std::is_base_of](http://en.cppreference.com/w/cpp/types/is_base_of) together with [static_assert](http://en.cppreference.com/w/cpp/language/static_assert). Boost provides c++03 alternatives. – juanchopanza Oct 28 '12 at 14:26
  • Oh, could you please provide an short example of your last idea? – danijar Oct 28 '12 at 14:42
  • 1
    @sharethis something like `static_assert(std::is_base_of::value, "T is not derived from Data");` but actually, I can't think of a situation where the code above would compile unless `T` is a base of `Data`. – juanchopanza Oct 28 '12 at 16:03
1

Using unique_ptr means committing to working with dynamically allocated objects. (Not quite true: you could supply a custom deleter that doesn't call delete, but that becomes part of the type; this means you would be committing to working with non-dynamically allocated objects.)

void Manager::Add(String title, unique_ptr<Data> d) {
    List[title] = d;
}

Call it as:

Manager manager;
unique_ptr<Data> pos(new Position);
// Set it up
manager.Add("position data", pos);

Note: If you want to be able to do anything with your data objects, you probably want to declare at least one virtual function inside class Data.

j_random_hacker
  • 50,331
  • 10
  • 105
  • 169
  • Thanks. Maybe `unique_ptr` isn't the right option to go for me. Because I want to provide a `manager.Get(string Name);` returning a pointer to that data object. What do you think? – danijar Oct 28 '12 at 14:15
  • 1
    Whoops, I forgot that `List` is actually a `map` :-P I've edited the `Add()` implementation to fix that. `unique_ptr` behaves just like a pointer, so you can still write `unique_ptr Manager::Get(String title) { return List[title]; }`, exactly as you would for a plain pointer. All `unique_ptr` does is make sure that dynamically allocated pointees are cleaned up automatically. – j_random_hacker Oct 28 '12 at 14:20
  • So there wouldn't be a problem since there were two pointers then? One in the list of the manager and one returned by the get function. That doesn't sound like a *unique* pointer. – danijar Oct 28 '12 at 14:21
  • 1
    Ah. If you want to let the caller of `Get()` have access to the object without claiming ownership of it, I would suggest using a reference instead: `Data& Manager::Get(String title) { return *List[title]; }`. You could alternatively use a plain pointer (this can be obtained by calling `get()` on a `unique_ptr`). – j_random_hacker Oct 28 '12 at 14:27
0

Define the function as following:

void Manager::Add(String title,Position pos){ /*snip*/ }

Then you can use the function as following:

Manager manager;
Position posData;
//setup the posData
manager.Add("position data",posData);
Lauri
  • 341
  • 1
  • 11
  • I can't do that. Since the manager must be able to store all types derived from `data` what I need is an generalized solution. Moreover I don't want to create an instance in my main function. – danijar Oct 28 '12 at 14:01
  • You could pass temporary `Position` class that you insert into the map. – Lauri Oct 28 '12 at 14:03
  • I could but if there is a way of not doing that I would prefer that. Nevertheless, the manager must be able to add every possible object of a derived class of data, not just Position. – danijar Oct 28 '12 at 14:05
  • Then `Position` could be just changed to `Data`. But it seems like you will find the answer you like from the other answers. – Lauri Oct 28 '12 at 14:10