Хранить производные объекты класса в переменных базового класса

Я хотел бы хранить экземпляры нескольких классов в векторе. Поскольку все классы наследуются от одного и того же базового класса, это должно быть возможно.

Представьте себе эту программу:

#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;
}
Другие вопросы по тегам