У меня есть указатель базового класса, указывающий на объект производного класса. Я звоню foo()
функция при помощи двух различных путей в коде ниже. Почему делает Derived::foo()
позвониться в первом случае? Не был должен (*obj).foo()
звонить Base::foo()
функционируйте, поскольку это было уже разыменовано?
class Base
{
public:
Base() {}
virtual void foo() { std::cout << "Base::foo() called" << std::endl; }
virtual ~Base() {};
};
class Derived: public Base
{
public:
Derived() : Base() {}
virtual void foo() { std::cout << "Derived::foo() called" << std::endl; }
virtual ~Derived() {};
};
int main() {
Base* obj = new Derived();
// SCENARIO 1
(*obj).foo();
// SCENARIO 2
Base obj1 = *obj;
obj1.foo();
return 0;
}
// СЦЕНАРИЙ 1 (* объект) .foo ();
Обратите внимание, что
obj
здесь неправильно, поскольку он относится не к объекту, а к указателю , (* ptr) .foo ()
- это просто обходной способ выполнить ptr-> foo ()
. * ptr
приводит не к объекту, а к ссылке Base &
на объект. И вызов виртуальной функции через ссылку подлежит динамической диспетчеризации, точно так же, как такой вызов через указатель.
// СЦЕНАРИЙ 2 База obj1 = * ptr; obj1.foo ();
Здесь вы создаете совершенно новый объект с помощью нарезки : у него просто есть части базового класса * ptr
. Вместо этого вам нужно следующее:
Base& ref = *ptr;
ref.foo();
Это поможет, если вы немного подумаете о реализации. Во втором сценарии вы фактически создаете новый объект типа Base, который будет поставляться с новой таблицей виртуальных функций. Но в первом сценарии * obj
будет «указывать на» или, скорее, ссылаться на объект, который все еще имеет таблицу виртуальных функций объекта типа Derived.
В дополнение к другим ответам.
Технический термин для описания того, что происходит в вашем Сценарии 2, - это нарезка объектов.
Вот запись в Википедии:
http://en.wikipedia.org/wiki/Object_slicing
А вот еще один вопрос о stackoverflow при нарезке объектов:
В первом случае производная версия foo ()
будет вызываться по очевидным причинам, объясненным выше. В дополнение к другим ответам * (* Obj) .func () *
является синонимом * Obj-> func () *
.
Во втором случае создается экземпляр нового объекта класса Base
через конструктор копирования, и, поскольку это объект класса Base
, он вызовет класс Base
. версия foo ()
.
Что вы подразумеваете под «поскольку он уже был разыменован»?
Указатель базового класса obj указывает на объект производного класса, и, поскольку вы объявили функцию foo () виртуальной, производный класс foo () будет называется.
Сценарий 2 создает совершенно новый объект типа Base. Таким образом, когда мы выполняем obj1.foo()
, объект вообще не является Derived; мы никак не сможем вызвать функцию Derived.
В сценарии 1, однако, объект на самом деле является экземпляром Derived, к которому мы обращаемся через указатель Base. Это именно та ситуация, для которой предназначены виртуальные функции; используется реализация производного класса.
(Довольно странный вопрос. Скорее всего, кто-нибудь спросит, почему во втором случае Derived :: foo
не вызывается.)
В языке C ++, какая версия виртуальной функции вызывается, полностью не зависит от того, что было, а что не было «разыменовано». Разыменование не имеет никакого значения. Единственное, что имеет значение, - это динамический тип объекта, используемого в вызове.
В первом случае вызывается Derived :: foo
, потому что динамический тип объекта * obj
- Derived
.
Во втором случае динамический тип obj1
- Base
, поэтому вызывается Base :: foo
.
Другими словами, все работает как положено. Это заставляет задуматься о том, что заставило вас задать свой вопрос. Что заставило вас ожидать чего-то другого?
Полиморфизм работает со ссылками (результатом разыменования указателя) так же, как с указателями.