Вызов методов подкласса из суперкласса в векторе C++

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

Я изучил Java и не знаю, почему это делается на C++. Я попытался переписать код, используя вектор указателей суперкласса и приведя указатель подкласса к суперклассу. Затем доступ к этому через указатели работает.

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

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

Моя структура:

class a { vector vec of class X,
    method to create and add an instance of X into vector vec,
    method to create and add an instance of Y into vector vec }
class X { talk() }
class Y : public X { talk() }

Код, демонстрирующий то, что я в идеале хочу сделать, но показывающий, что он сломан, только вызывая метод суперкласса:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>

class A {
  public:
    virtual void talk() { printf("A\n"); }
};

class B: public A {
  public:
      void talk() { printf("B\n"); }
};

int main(void) {
    std::vector<A> vec;
    std::vector<A*> vec2;
    A a;
    B b;
    a.talk();
    b.talk();

    vec.push_back(a);
    vec.push_back(b);
    vec2.push_back(&a);
    vec2.push_back(&b);

    for(int i = 0; i < vec.size(); i++) {
        vec[i].talk();
        vec2[i]->talk(); //bad but short for example
    }

}

person Chewett    schedule 19.07.2014    source источник
comment
Вам нужны указатели (возможно, интеллектуальные указатели, такие как std::unique_ptr), чтобы получить полиморфное поведение. См. разделение объектов. Это работает в Java, потому что там MyClass obj эффективно объявляет указатель на MyClass.   -  person Igor Tandetnik    schedule 19.07.2014
comment
talk() не является виртуальным в A, поэтому нет метода для переопределения. Компилятор будет использовать статический тип, а именно A.   -  person 0x499602D2    schedule 19.07.2014
comment
@ 0x499602D2 Ошибка в первой версии этого кода исправлена, добавлен виртуальный, но все еще не работает должным образом.   -  person Chewett    schedule 19.07.2014
comment
@IgorTandetnik А, это имеет смысл, хотите добавить это в качестве ответа? Есть ли еще литература, которую я могу прочитать?   -  person Chewett    schedule 19.07.2014


Ответы (5)


Чтобы получить желаемое полиморфное поведение, вам нужно добавить спецификатор virtual к функциям в базовом классе, которые вы хотите переопределить в производных классах.

class A {
public:
    virtual void talk() { printf("A\n"); }
};

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

class B: public A {
public:
    virtual void talk() override { printf("B\n"); }
//                      ^ Compiler will report an error if base class' function
//                        is not virtual.
};

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

std::vector<A> vec;
/* ... */
B b;
/* ... */
vec.push_back(b); // Slicing. Information only in B is lost.

Живой пример с использованием спецификатора virtual

Текущий пример без спецификатора virtual

person Snps    schedule 19.07.2014
comment
Это не работает, к вашему сведению, поскольку я все еще получаю AB AA AB на выходе. - person Chewett; 19.07.2014
comment
Изначально код был неправильно набран, исправил в своей IDE, но не здесь, исправил сейчас. - person Chewett; 19.07.2014
comment
Возможно, вы захотите удалить первую часть вашего ответа, так как это не проблема (просто копирование кода было лол). В остальном выглядит отлично, спасибо :) - person Chewett; 19.07.2014
comment
@Chewett Спецификатор virtual требуется для полиморфного поведения. Без него вызов talk() всегда будет вызывать A::talk независимо от того, вызывается ли он из экземпляра B. Спецификатор override не обязателен, но полезен, поскольку сообщает компилятору, что вы пытаетесь выполнить (хотя он может включить оптимизацию, но не уверен). - person Snps; 19.07.2014
comment
Я не думал, что это требуется для B, поскольку тогда это означает, что любые другие подклассы B не смогут переопределить метод talk() B. то, что я хочу, чтобы произошло. Этот вопрос сосредоточен исключительно на переопределении основного базового класса. Итак, вам нужен виртуальный на B в приведенном выше случае? Мой компилятор говорит «нет», поскольку он компилирует и выдает ожидаемое поведение в случае указателя. - person Chewett; 19.07.2014
comment
@Chewett Виртуальное свойство будет автоматически передано функциям переопределения, т. Е. Если A::talk является виртуальным, то B::talk также будет виртуальным, независимо от того, опустите ли вы спецификатор virtual (оно будет тайно виртуальным). Из-за этого для ясности лучше указывать переопределяющие функции как виртуальные. - person Snps; 19.07.2014
comment
Я не знал этого, спасибо. Это означает, что на самом деле добавление только к одному не имеет смысла, так как оно будет унаследовано в будущем и просто сделает его более неясным? - person Chewett; 19.07.2014

Вы должны объявить методы как virtual, чтобы иметь возможность переопределить их в подклассе. Также рекомендуется сделать деструктор виртуальным.

class A {
public:
   virtual void talk() { printf("A\n"); }
   virtual ~A(){}
};

class B: public A {
public:
   // using virtual is not really necessary here, but it's good for clarity.
   virtual void talk() { printf("B\n"); }
};
person rashmatash    schedule 19.07.2014
comment
Это была ошибка при загрузке кода, я исправил это перед загрузкой, и у меня все еще есть вывод AB AA AB - person Chewett; 19.07.2014

Метод должен быть виртуальным.
В Java методы являются виртуальными по умолчанию.

class A {
  public:
    virtual void talk() { printf("A\n"); }
};

class B: public A {
  public:
      virtual void talk() override { printf("B\n"); } //override key word is in C++  0x and above
};
person Yochai Timmer    schedule 19.07.2014
comment
Это была ошибка при загрузке кода, я исправил это перед загрузкой, и у меня все еще есть вывод AB AA AB - person Chewett; 19.07.2014

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

person Logicrat    schedule 19.07.2014
comment
Это была ошибка при загрузке кода, я исправил это перед загрузкой, и у меня все еще есть вывод AB AA AB - person Chewett; 19.07.2014

Если вы не используете указатель, вы получите «нарезку объекта» при копировании объекта в вектор. Это уменьшает объект до базового типа, объявленного в аргументе векторного шаблона. Таким образом, нет подкласса, поэтому нет метода подкласса для вызова, даже если метод виртуальный.

person user207421    schedule 19.07.2014