Хранить производные объекты класса в переменных базового класса
Я хотел бы хранить экземпляры нескольких классов в векторе. Поскольку все классы наследуются от одного и того же базового класса, это должно быть возможно.
Представьте себе эту программу:
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
virtual void identify ()
{
cout << "BASE" << endl;
}
};
class Derived: public Base
{
public:
virtual void identify ()
{
cout << "DERIVED" << endl;
}
};
int main ()
{
Derived derived;
vector<Base> vect;
vect.push_back(derived);
vect[0].identify();
return 0;
}
Я ожидал, что он выведет "DERIVED", потому что метод "identifier" является виртуальным. Вместо этого 'vect[0]' кажется "базовым" экземпляром и печатает
БАЗА
Я думаю, я мог бы написать свой собственный контейнер (вероятно, производный от вектора), который мог бы сделать это (возможно, содержит только указатели...). Я просто хотел спросить, есть ли более C++ метод для этого. И я хотел бы быть полностью совместимым с вектором (просто для удобства, если другие пользователи когда-либо будут использовать мой код).
6 ответов
То, что вы видите, это нарезка объектов.
Вы сохраняете объект класса Derived в векторе, который должен хранить объекты базового класса, это приводит к нарезке объектов, и специфичные для производного класса члены сохраняемого объекта обрезаются, таким образом объект, сохраненный в векторе, просто действует как Объект Базового класса.
Решение:
Вы должны хранить указатель на объект Базового класса в векторе:
vector<Base*>
Сохраняя указатель на базовый класс, не будет никакого среза, и вы также сможете добиться желаемого полиморфного поведения.
Так как вы просите C++ish
В этом случае правильный подход - использовать подходящий Smart-указатель вместо хранения необработанного указателя в векторе. Это гарантирует, что вам не нужно вручную управлять памятью, RAII сделает это автоматически.
TL;DR: Вы не должны наследовать от публично копируемого / подвижного класса.
Фактически возможно предотвратить нарезку объектов во время компиляции: базовый объект не должен быть копируемым в этом контексте.
Случай 1: абстрактная база
Если база является абстрактной, то ее нельзя создать, и, таким образом, вы не сможете испытать нарезку.
Случай 2: бетонное основание
Если база не абстрактная, ее можно скопировать (по умолчанию). У вас есть два варианта:
- предотвратить копирование вообще
- разрешить копирование только для детей
Примечание: в C++11 операции перемещения вызывают ту же проблему.
// C++ 03, prevent copy
class Base {
public:
private:
Base(Base const&);
void operator=(Base const&);
};
// C++ 03, allow copy only for children
class Base {
public:
protected:
Base(Base const& other) { ... }
Base& operator=(Base const& other) { ...; return *this; }
};
// C++ 11, prevent copy & move
class Base {
public:
Base(Base&&) = delete;
Base(Base const&) = delete;
Base& operator=(Base) = delete;
};
// C++ 11, allow copy & move only for children
class Base {
public:
protected:
Base(Base&&) = default;
Base(Base const&) = default;
Base& operator=(Base) = default;
};
Вы испытываете нарезку. Вектор копирует derived
объект, новый тип Base
вставлен.
Я бы использовал vector<Base*>
хранить их. Если вы говорите vector<Base>
, нарезка будет происходить.
Это означает, что вам нужно будет удалить сами объекты после того, как вы удалите указатели из вашего вектора, но в остальном у вас все будет хорошо.
// Below is the solution by using vector<Based*> vect,
// Base *pBase , and initialized pBase with
// with the address of derived which is
// of type Derived
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
virtual void identify ()
{
cout << "BASE" << endl;
}
};
class Derived: public Base
{
public:
virtual void identify ()
{
cout << "DERIVED" << endl;
}
};
int main ()
{
Base *pBase; // The pointer pBase of type " pointer to Base"
Derived derived;
// PBase is initialized with the address of derived which is
// of type Derived
pBase = & derived;
// Store pointer to object of Base class in the vector:
vector<Base*> vect;
// Add an element to vect using pBase which is initialized with the address
// of derived
vect.push_back(pBase);
vect[0]->identify();
return 0;
}
Как и все остальные, упомянутые здесь, вы не можете выполнить вставку производного объекта в вектор базы из-за нарезки объекта, которая произойдет при построении копии.
Если цель состоит в том, чтобы избежать выделения памяти, вы можете использоватьstd::variant
, но вектор больше не будет базового класса
using HierarchyItem = std::variant<Base, Derived>;
int main()
{
vector<HierarchyItem> vect;
vect.push_back(Derived());
std::visit([](auto &&hier_item){ hier_item.identify(); }, vect[0]);
return 0;
}