Полиморфизм С++ без указателей

Предположим, что у меня есть базовый класс Animal с виртуальными функциями и некоторыми производными классами (Cat, Dog и т.д.). Реальные производные классы содержат 4-8 байтов данных. Я хочу сохранить std::list<Animal>, который фактически содержит элементы, которые являются производными объектами. Я хочу избежать создания множества небольших объектов в куче, используя новые.

Есть ли какой-либо шаблон дизайна, который можно использовать для достижения этого?

EDIT: Мои идеи для реализации этого

  • create std::deque<Cat>, std::deque<Dog>,...; store std::list<Animal*>, который содержит указатели от deques; Я использую std::deque, потому что полагаю, что он имеет хорошее управление памятью с кусками объектов;

Ответ 1

В конечном счете, нет.

Полиморфизм работает только с нецензурными типами: ссылки и указатели. И так как ссылки могут быть связаны только один раз, вы не можете использовать их в стандартных контейнерах. Это оставляет вас указателями.

Вы атакуете проблему не на том конце. Если вас беспокоит накладные расходы на выделение большого количества мелких объектов (и я предполагаю, что это является законной проблемой. То есть у вас есть фактические данные профилирования или достаточный опыт, чтобы знать, что это является проблемой для вашего конкретного приложения), затем вы должны это исправить. Измените, как вы распределяете память для этих объектов. Сделайте небольшую кучу выделения или что-то в этом роде.

По общему признанию, в этом отношении недостающие распределители pre-С++ 0x отсутствуют, поскольку они должны быть без гражданства. Но для ваших целей вы должны иметь возможность справиться с этим.


Из вашего редактирования:

Это ужасная идея. Стирание с std::deque в любом месте, кроме начала или конца, приведет к недействительности каждого указателя в std::list.

Учитывая ваш комментарий, эта идея функциональна. Однако, имея все эти различные блоки памяти для разных объектов, кажется, идет вразрез со всей точкой наследования. В конце концов, вы не можете просто написать новый тип Animal и вставить его в std::list; вам необходимо обеспечить управление памятью.

Вы уверены, что полиморфизм, основанный на наследовании, вам нужен? Вы уверены, что некоторая другая методология не будет работать так же хорошо?

Ответ 2

Я понимаю, что этот вопрос старый, но я нашел несколько довольно приятное решение.

Предположение:

Вы знаете все производные классы заранее (учитывая ваше редактирование, это правда).

Trick:

Использование boost:: variant (http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html)

Примеры классов:

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;
    }
};

Пример варианта/посетителя:

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;
    }
};

Использование примера:

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();
    }
}

Пример вывода

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!

Ответ 3

Если вы беспокоитесь о распределении множества небольших объектов кучи, тогда вектор может быть лучшим выбором контейнера, а не списком и деком. list будет выделять node в куче каждый раз, когда вы вставляете объект в список, тогда как вектор будет хранить все объекты в смежной области памяти в куче.

Если у вас есть:

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);
}

Затем все собаки и кошки хранятся в двух смежных областях памяти на куче.

Ответ 4

Возможно, вы могли бы сделать что-то с простым классом-оболочкой для объединения, содержащего супер-набор данных, необходимых для каждого случая. Это будет содержать указатель на общие strategy объекты, содержащие код для разных типов поведения. Таким образом, кошка является объектом класса PolyAnimal с видамиName = "cat", PredatorFeedingStrategy и т.д.

Вероятно, лучшим способом решения основной проблемы является создание соответствующих пользовательских распределителей для более естественного дизайна.