Best way for derived classes to carry different data types in C++
Asked Answered
X

4

6

What is the most elegant way to provide an interface in C++ that accepts derived class types that carry with them different data type members that then need to be retrieved later. The example below illustrates this where the Container class provides methods to "post" an Item that will be some kind of derived variant of BaseItem. Later on I want to get the derived Item back and extract its value.

The main thing I want is for the Container interface (post and receive) to stay the same in the future while allowing different "Item" derived types to be defined and "passed" through it. Would template be better for this somehow; I'd rather not use RTTI. Maybe there is some simple, elegant answer to this, but right now I'm struggling to think of it.

class ItemBase {
  // common methods
};

class ItemInt : public ItemBase
{
  private:
    int dat;
  public:
    int get() { return dat; }  
};

class ItemDouble : public ItemBase
{
  private:
    double dat;
  public:
    double get() { return dat; }  
};

class Container {
 public:
   void post(int postHandle, ItemBase *e);      
   ItemBase* receive(int handle); // Returns the associated Item
};

int main()
{
   ItemInt *ii = new IntItem(5);
   Container c;
   c.post(1, ii);

   ItemInt *jj = c.receive(1); 
   int val = jj->get();  // want the value 5 out of the IntItem
}
Xylotomy answered 4/1, 2012 at 21:45 Comment(6)
Try Boost.any, which does something very similar.Kourtneykovac
Also note that the code as posted has the problem of each object's get would have a different return type. If you can't make them the same type, you'll have to return a stringstream or something.Joanjoana
Yes I agree, but the get() function is not a virtual function. It is "separate functionality" that each derived class "adds". There is other common functionality that make the inheritance "needful"Xylotomy
@innocent_bystander: in that case your main must cast c.receive() to an to an IntItem before it can call .get(), since that conversion is not implicit.Joanjoana
Do you know what action you want to perform when you "receive" ahead of time?Reformation
@Node: Yes the caller of receive knows what type to expect and what to do with it.Xylotomy
S
6

This is definitely a candidate for generic programming, rather than inheritance. Remember, generics (templates) are ideal when you want identical handling for different data types. Your ItemInt and ItemDouble classes violate OO design principles (the get() method returns different data types depending on what the actual subtype is). Generic programming is built for that. The only other answer would be a tagged data type, and I personally avoid those like the plague.

Shingle answered 4/1, 2012 at 21:52 Comment(2)
I agree we could create a template Item<T> and have get() return T. But what type would the 2nd argument to post() be? I want it to accept Items with any template parameter, T.Xylotomy
@innocent_bystander: Generally I would make Container::post and Container::recieve both templates, and skip the ItemBase altogeather.Joanjoana
A
1

How about?

template<typename T>
class Item
{
  private:
    T dat;
  public:
    T get() { return dat; }  
};

class Container {
 public:
   template<typename T>
   void post(int postHandle, Item<T> *e);      

   template<typename T>
   Item<T>* receive(int handle); // Returns the associated Item
};

int main()
{
   Item<int> *ii = new Item<int>(5);
   Container c;
   c.post(1, ii);

   Item<int> *jj = c.receive<int>(1); 
   int val = jj->get();  // want the value 5 out of the IntItem
}
Addis answered 4/1, 2012 at 22:20 Comment(0)
A
0

Your Container class looks suspiciously like a std::map. It looks to me like your ItemBase class is just a different name for "Object", the universal base class, which I think is not much different from (or better than) void*. I would avoid trying to contain items of different type in a single container. If your design seems to call for doing so, I'd rethink your design.

Arriaga answered 4/1, 2012 at 22:17 Comment(0)
B
0

A pure template approach doesn't work because you apparently want to have mixed types in your container. You could work with something like Boost's any although I think you need to restore the actual. What I think is called for in this case is a base class exposing the type-independent and virtual methods plus a templatized derived class to hold the actual items:

class Base {
public:
    virtual ~Base() {}
    virtual void post() = 0;
};

template <typename T>
class Item: public Base {
public:
    Item(T const& value): value_(value) {}
    void post() { std::cout << "posting " << this->value_ << "\n"; }
private:
    T value_;
};

This approach avoids the need to write any derived Item class for another value type. To make creation of these beast easier you probably want to create a suitable creation function as well, e.g.

template <typename T>
std::unique_ptr<Base> make_item(T const& value) {
    return std::unique_ptr<Base>(new Item<T>(value));
}

A std::unique_ptr<Base> is returned to make sure that the allocated object is released (if you don't use C++2011 you can used std::auto_ptr<T> instead). This type can easily be converted to other pointer types, e.g. to a std::shared_ptr<Base> which is a better suited to be put into a container.

Babi answered 4/1, 2012 at 23:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.