Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ polymorphism without pointers

Tags:

c++

Suppose that I have a base class Animal with virtual functions and some derived classes (Cat, Dog, etc.). The real derived classes contain 4-8 bytes of data. I want to store a std::list<Animal> which actually contains items which are derived objects. I want to avoid the creation of many small objects on the heap using new.

Is there any design pattern which can be used to achieve this?

EDIT: My ideas to implement this

  1. create std::deque<Cat>, std::deque<Dog>, ...; store std::list<Animal*> which contains pointers from the deques; I use the std::deque because I suppose that it has a good memory management with chunks of objects;
like image 554
Andrei Bozantan Avatar asked Aug 28 '11 20:08

Andrei Bozantan


People also ask

Do you need pointers for polymorphism?

(*) Genericity, the type of polymorphism provided by templates, doesn't need pointers nor references.

Does polymorphism only work with pointers?

Polymorphism only works with non-value types: references and pointers. And since references can only be bound once, you cannot really use them in standard containers.

Can we achieve polymorphism without inheritance?

Real polymorphism in General can not be acheived without inheritance. Languages that are not object-oriented provide forms of polymorphism which do not rely on inheritance (i.e parametric polymorphism).

What is polymorphism in C?

Polymorphism example in C++ Polymorphism is a key feature of object oriented programming that means having multiple forms. This is divided into compile time polymorphism and runtime polymorphism in C++. An example of compile time polymorphism is function overloading or operator overloading.


3 Answers

Ultimately, no.

Polymorphism only works with non-value types: references and pointers. And since references can only be bound once, you cannot really use them in standard containers. That leaves you with pointers.

You're attacking the problem at the wrong end. If you are concerned about the overhead of allocating lots of small objects (and I'm assuming that this is a legitimate concern. That is, you have actual profiling data or sufficient experience to know it is a concern for your specific application), then you should fix that. Change how you're allocating memory for these objects. Make a small allocation heap or something.

Admittedly, pre-C++0x's allocators are somewhat lacking in this regard, since they have to be stateless. But for your purposes, you should be able to deal with it.


From your edit:

That is a terrible idea. Erasing from a std::deque at anywhere but the start or end will invalidate every pointer in your std::list.

Given your comment, this idea is functional. However, having all of these different memory blocks for different kinds of objects seems to go against the whole point of inheritance. After all, you can't just write a new type of Animal and slip it into the std::list; you have to provide memory management for it.

Are you sure that inheritance-based polymorphism is what you need here? Are you sure that some other methodology would not work just as well?

like image 148
Nicol Bolas Avatar answered Oct 09 '22 02:10

Nicol Bolas


I realize that this question is old, but I found a somewhat pretty solution.

Assumption:

You know all the derived classes in advance (given your edit, this is true).

Trick:

Using boost::variant (http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html)

Example classes:

class Animal {
public:
    virtual void eat() = 0;
};

class Cat : public Animal {
    virtual void eat() final override {
        std::cout << "Mmh, tasty fish!" << std::endl;
    }
};

class Dog: public Animal {
    virtual void eat() final override {
        std::cout << "Mmh, tasty bone!" << std::endl;
    }
};

Example variant/visitor:

typedef boost::variant<Cat, Dog> AnimalVariant;

class AnimalVisitor : public boost::static_visitor<Animal&> {
public:
    Animal& operator()(Cat& a) const {
        return a;
    }

    Animal& operator()(Dog& a) const {
        return a;
    }
};

Example usage:

std::vector<AnimalVariant> list;
list.push_back(Dog());
list.emplace_back(Cat());

for(int i = 0; i < 5; i++) {
    for(auto& v : list) {
        Animal& a = v.apply_visitor(AnimalVisitor());
        a.eat();
    }
}

Example output

Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!
Mmh, tasty bone!
Mmh, tasty fish!
like image 9
Draziw Avatar answered Oct 09 '22 02:10

Draziw


If you're worried about allocating many small heap objects, then a vector may be a better choice of container rather than a list and a deque. list will allocate a node on the heap each time that you insert an object into the list, whereas vector will store all objects in a contiguous region of memory on the heap.

If you have:

std::vector<Dog> dogs;
std::vector<Cat> cats;

std::vector<Animal*> animals;

void addDog(Dog& dog, std::vector<Dog>& dogs, std::vector<Animal*>& animals) {
  dogs.push_back(dog);
  animals.push_back(&dog);
}

Then all dogs and cats are stored in two contiguous region of memory on the heap.

like image 2
Brian Coleman Avatar answered Oct 09 '22 04:10

Brian Coleman