Solved

polymorphism?

Posted on 2007-03-29
11
232 Views
Last Modified: 2010-04-01
Hi,

I have a few classes all derived from a base. A container class keeps some vectors of these container classes. Right now I have an Add() method for each class type to the container - but it's just the same code repeated, with a different class type of course. Is there a way I can have one Add() method and I can throw any derived type at it? I don't want to have 20  add methods for each class type:

class BASE {

};

class Apple : public BASE {
    bool operator==(const Apple& a) const {
        return ...;
    }
};

class Orange : public BASE {
    bool operator==(const Orange& o) const {
        return ...;
    }
};

class Container {

    vector<Apple> m_vApples;
    vector<Orange> m_vOranges;

    void AddApple(const Apple& apple)
    {
        if (find(m_vApples.begin(), m_vApples.end(), apple) != m_vApples.end()) {
            throw ("already exists");
        }
        m_vApples.push_back(apple);
    }

    void AddOrange(const Orange& orange)
    {
        if (find(m_vOranges.begin(), m_vOranges.end(), orange) != m_vOranges.end()) {
            throw ("already exists");
        }
        m_vOranges.push_back(orange);
    }
};

Yeah and I will have like 20 different class types in the container class - so I don't want to have 20 Add?? methods.

Thanks
0
Comment
Question by:DJ_AM_Juicebox
11 Comments
 
LVL 4

Expert Comment

by:MikeGeig
Comment Utility
Hello,

Sounds to me like what you need is templating. Templating essentially allows you to take one function, and write it to cover many variable types.

Here is a usefull guide
http://www.is.pku.edu.cn/~qzy/cpp/vc-stl/templates.htm

Basically, your functions are written generically, so that the compiler can choose which types to use at run-time.
0
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 500 total points
Comment Utility
You could do:

    void Container::AddFruit(const Base& fruit)
    {
        switch(fruit.fruitId())
        {
        case APPLES:
            if (find(m_vApples.begin(), m_vApples.end(), dynamic_cast<const Apple&> (fruit)) != m_vApples.end()) {
                throw ("already exists");
            }
            m_vApples.push_back(dynamic_cast<const Apple&>  (fruit);
            break;
        case ORANGES:
            if (find(m_vOranges.begin(), m_vOranges.end(), dynamic_cast<const Orange&> (fruit)) != m_vOranges.end()) {
                throw ("already exists");
            }
            m_vOranges.push_back(dynamic_cast<const Orange&>  (fruit));
            break;
        }
        ...
    }

but actually it is worse than 20 functions.

The problem isn't your add function but the 20 different vectors which each take the derived class by value. Because of that you can't make it generic (beside you would make some real dirty casts).

You could store Base* pointers for each derived class. The addFruit function would  take Base& and make a copy of the given object by means of a virtual function.

typedef bool (*CompareFruit)(const Base* pf1, const Base* pf2);

class Container {

    enum Fruits { APPLE, ORANGE, ..., MAX_FRUIT };

    vector<Base*> m_vFruits[MAX_FRUIT];
    CompareFruit  m_vCompFunc[MAX_FRUIT];


    void AddFruit(const Base& fruit)
    {
        vector<Base*>& current = m_vFruits[fruit.fruitId()];

        if (find(current.begin(), current.end(), &fruit, m_vCompFunc[fruit.fruitId()]) != current.end()) {
                throw ("already exists");
            }
        current.push_back(fruit.makeCopy());
    }
};

Hope, it's understandable.

Regards, Alex

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
In the above sample each derived class needs to implement a compare function which can be added to the array of compare functions. The compare functions are necessary if storing pointers cause find would compare pointers instead of values if not doing so. If the id of each 'frruit' is a baseclass member you could do all with a compare function provided by Base or Container.

If you want the array of vectors to be a dynamic vector you need to use a map rather than a fixed-sized array. Each derived class would register at the containg passing its class name and the compare function (and copy function if you want). Then addFruit would retrieve the classname via virtual function from fruit, and use it as a key to the static map where all derived classes have registered. The value of the map is a struct that contains the vector, the compare function (and the copy function),

Regards, Alex
0
 
LVL 86

Expert Comment

by:jkr
Comment Utility
I'd use a virtual base interface for that, e.g.

class BASE {

public:

      enum Type {

            tyApple,
            tyOrange
      };

      virtual const Type GetType () const = 0;

};

class Apple : public BASE {

      public:

      virtual const Type GetType () const { return tyApple;}

    bool operator==(const Apple& a) const {
        return tyApple == a.GetType() && AllMembersMatch();
    }
};

class Orange : public BASE {

    public:

      virtual const Type GetType () const { return tyOrange;}

      bool operator==(const Orange& o) const {
        return tyOrange == o.GetType() && AllMembersMatch();
    }
};

class Container {

    vector<BASE&> m_vElements;

    void AddElement(const BASE& elem)
    {
        if (find(m_vElements.begin(), m_vElements.end(), elem) != m_vElements.end()) {
            throw ("already exists");
        }
        m_vElements.push_back(elem);
    }

};
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> Sounds to me like what you need is templating
Yes, Mike is right. A template would solve that you only have to provide one add function. However as template functions cannot be made member functions you either need a global template function thus moving the problem of instantiating each template to the caller *or* you provide a member function which takes a const Base& and have to call each template using a switch like I did above.

void Container::add(const Base& fruit)
{
     switch(fruit.getFruitId())
     {
     case APPLE:      addFruit(dynamic_cast<const Apple&> (fruit)); break;
     case ORANGE:  addFruit(dynamic_cast<const Orange&> (fruit)); break;
     ....
     }
}

Though it is a one-liner for each case it isn't very pretty.

Note, you may pass fruit without cast and make the dynamic_cast in the template. However, you would have to add the template type then:

     case APPLE:      addFruit<const Apple&> (fruit); break;

Regards, Alex
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> vector<BASE&> m_vElements;

A vector can't store references. You would need to use pointers as I showed above.


0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> vector<BASE*> m_vElements;

Moreover, all fruits are in one vector what is different to the spec....
0
 
LVL 3

Expert Comment

by:Darrylsh
Comment Utility
Ok here is a templated version and a vector of base version

#include <string>
#include <vector>
#include <algorithm>


class BASE
{
public:
      std::string m_name;
      int m_id;

      bool operator==(const BASE B) const
      {
        return B.m_name == m_name && B.m_id == m_id;
      }
};

class Apple : public BASE
{
public:
      Apple(int id)
      {
            m_name="apple";
            m_id=id;
      }

};

class Orange : public BASE
{
public:
      Orange(int id)
      {
            m_name="orange";
            m_id=id;
      }
};

template <typename T>
class Container // can hold fruit defined in template
{      
      std::vector<const T> m_vFruit;

public:    
    void AddFruit(const T& fruit)
    {
        if (find(m_vFruit.begin(), m_vFruit.end(), fruit) != m_vFruit.end())
            {
            throw ("already exists");
        }
        m_vFruit.push_back(fruit);
    }
};

class Container2 // can hold any fruit derived from base
{
      std::vector<const BASE> m_vFruit;
public:
      void AddFruit(const BASE& fruit)
      {
            if (std::find(m_vFruit.begin(), m_vFruit.end(), fruit) != m_vFruit.end())
            {
            throw ("already exists");
        }
        m_vFruit.push_back(fruit);
    }
};



      
int main()
{
      Container<Apple> apple_container;
      Container<Orange> orange_container;
      Container<BASE> templated_fruit_container;

      Container2 fruit_container;

      Apple apple(1);
      Orange orange(1);
      


      apple_container.AddFruit(apple);
      orange_container.AddFruit(orange);

      templated_fruit_container.AddFruit(apple);
      templated_fruit_container.AddFruit(orange);
      
      fruit_container.AddFruit(apple);
      fruit_container.AddFruit(orange);
}
0
 
LVL 86

Expert Comment

by:jkr
Comment Utility
>>A vector can't store references. You would need to use pointers

Yes, that's right, but not too much of a difference, since the emphasis was put on the interface

>>Moreover, all fruits are in one vector

Yes, that was the idea.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> but not too much of a difference
One difference is that you have to make copies if not taking the *pointer* given what normally is a bad doing for a container.  Another difference is that you can't check for duplicates without providing a compare function for each pointer type.

The last argument may spoil the idea of putting all fruits to one container as it is difficult to compare apples with oranges, isn't it?

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> int main()
>>>> {
>>>>      Container<Apple> apple_container;
>>>>      Container<Orange> orange_container;

No, obviously you will not define the containers at function level if you already invested so much in polymophism...

So, you would need a

class FruitContainer
{
    Container<Apple> apple_container;
    Container<Orange> orange_container;
    ...
};

and we are at the begin again ....

Regards, Alex
0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, wh…
Introduction This article is a continuation of the C/C++ Visual Studio Express debugger series. Part 1 provided a quick start guide in using the debugger. Part 2 focused on additional topics in breakpoints. As your assignments become a little more …
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now