Переопределяющая функция в C ++ не работает - PullRequest
6 голосов
/ 01 января 2011
#include <cstdio>
using namespace std;

class A {
public:
    virtual void func() { printf("A::func()"); }
};

class B : public A {
public:
    virtual void func() { printf("B::func()"); }
};

int main() {
  A a = *(A *)new B();
  a.func();
}

Вопрос прост: почему a->func() вызывает функцию в классе A, хотя a содержит объект класса B?

Ответы [ 7 ]

13 голосов
/ 01 января 2011
A a = *(A *)new B();
a.func();

Вот что происходит в этом коде, шаг за шагом:

  • new B(): новый объект типа B выделяется в свободном хранилище, в результате чего его адрес
  • (A*): адрес объекта приведен к A*, поэтому у нас есть указатель типа A*, фактически указывающий на объект типа B, который является допустимым.Все в порядке.
  • A a: здесь начинаются проблемы.Новый локальный объект типа A создается в стеке и создается с использованием конструктора копирования A::A(const A&), причем первым параметром является объект, созданный ранее.
  • Указатель на исходный объекттипа B теряется после этого оператора, что приводит к утечке памяти, так как он был выделен в свободном хранилище с new.
  • a.func() - метод вызывается для (локального) объекта классаA.

Если вы измените код на:

A& a = *( A*) new B();
a.func();

, то будет создан только один объект, его указатель будет преобразован в указатель типа A*, а затем разыменовани новая ссылка будет инициализирована с этим адресом .Вызов виртуальной функции будет затем динамически разрешен до B::func().


Но помните, что вам все равно нужно освободить объект, так как он был выделен с помощью new:

delete &a;

Что, кстати, будет правильным только в том случае, если у A есть виртуальный деструктор, для которого требуется B :: ~ B () (который, к счастью, здесь пуст, но вобщий случай) так же будет называться.Если у А нет виртуального деструктора, вам нужно освободить его:

delete (B*)&a;

Если вы хотите использовать указатель, то это то же самое, что и со ссылкой.Код:

A* a = new B(); // actually you don't need an explicit cast here.
a->func();
delete (B*)a; // or just delete a; if A has a virtual destructor.
5 голосов
/ 01 января 2011

Теперь, когда вы изменили свой фрагмент кода, проблема ясна.Полиморфизм (то есть виртуальные функции) вызывается только через указатели и ссылки.У вас нет ни одного из них.A a = XXX не содержит объект типа B, он содержит объект типа A.Вы "отрезали" B -ность объекта, выполнив приведение и разыменование этого указателя.

Если вы сделаете A *a = new B();, то получите ожидаемое поведение.

5 голосов
/ 01 января 2011

Проблема, с которой вы сталкиваетесь, является классической нарезкой объектов :

A a = *(A *)new B();

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


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

1 голос
/ 01 января 2011

Виртуальная диспетчеризация работает только с указателями или ссылочными типами:

#include <cstdio>
using namespace std;

class A {
public:
  virtual void func() { printf("A::func()"); }
};

class B : public A {
public:
  virtual void func() { printf("B::func()"); }
};

int main() {
  A* a = new B();
  a->func();
}
1 голос
/ 01 января 2011

Это может сделать этот трюк.

A &a = *(A *)new B();
a.func();

Или

A *a = new B();
a->func();
0 голосов
/ 03 января 2011

Поскольку вы выполняли нарезку, когда копировали динамически размещенный объект в объект a типа A (что также привело к утечке памяти).

a должен быть ссылкой (A&) вместо этого или просто держать указатель.

0 голосов
/ 01 января 2011

Проблема заключается в почтении и приведении B к A с A a = * (A *) new B ();

Вы можете исправить это, просто удалив * (A *), изменив егоto (A * a = new B ();), но я бы сделал еще один шаг, поскольку имя вашей переменной не подходит для создания экземпляров B.

Это должно быть

B *b = new B(); 
b->func(); 
...