Хранить объекты производного класса в переменных базового класса - PullRequest
50 голосов
/ 08 января 2012

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

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

#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 ++ метод для этого. И я хотел бы быть полностью совместимым с вектором (просто для удобства, если другие пользователи когда-либо будут использовать мой код).

Ответы [ 4 ]

62 голосов
/ 08 января 2012

То, что вы видите: Срезы объектов .
Вы храните объект класса Derived в векторе, который должен хранить объекты базового класса, это приводит к объектунарезка и специфичные для производного класса члены сохраняемого объекта обрезаются, поэтому объект, хранящийся в векторе, просто действует как объект базового класса.

Решение:

Вы должны хранить указатель на объект Базового класса в векторе:

vector<Base*> 

При хранении указателя на Базовый класс не будет никакого среза, и вы также сможете достичь желаемого полиморфного поведения.
Так каквы запрашиваете C++ish способ сделать это, правильный подход - использовать подходящий интеллектуальный указатель вместо хранения необработанного указателя в векторе.Это гарантирует, что вам не придется вручную управлять памятью, RAII сделает это автоматически.

5 голосов
/ 21 августа 2013

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;
};
5 голосов
/ 08 января 2012

Вы испытываете нарезку. Вектор копирует объект derived, вставляется новый тип Base.

3 голосов
/ 08 января 2012

Я бы использовал vector<Base*> для их хранения. Если вы скажете vector<Base>, произойдет нарезка.

Это означает, что вам придется самостоятельно удалять реальные объекты после того, как вы удалили указатели из вашего вектора, но в противном случае у вас все будет в порядке.

...