Ответ 1
Когда вы это сделаете:
A c = B();
Вы конвертируете значение B
в A
. Вы этого не хотите.
Вы должны сделать объект B
и получить доступ к нему с помощью указателя A
или ссылки, чтобы получить полиморфное поведение:
B b;
A& c = b;
Во-первых, я Java-кодер и хочу понять полиморфизм в С++. Я написал пример для учебных целей:
#include<iostream>
using namespace std;
class A
{
public:
virtual void foo(){ std::cout << "foo" << std::endl; }
};
class B : public A
{
public:
void foo(){ std::cout << "overriden foo" << std::endl; }
};
A c = B();
int main(){ c.foo(); } //prints foo, not overriden foo
Я ожидал, что overriden foo
будет напечатан, но это не так. Зачем? Мы переопределили метод foo
в class B
, и я подумал, что решение, которое следует вызывать, производится из типа среды выполнения, который в моем случае равен B
, но не статический тип ( в моем случае).
Живой пример там
Когда вы это сделаете:
A c = B();
Вы конвертируете значение B
в A
. Вы этого не хотите.
Вы должны сделать объект B
и получить доступ к нему с помощью указателя A
или ссылки, чтобы получить полиморфное поведение:
B b;
A& c = b;
В java у вас есть семантика значений с типами int
и float
, и у вас есть ссылочная семантика со всем остальным.
Это не так в C++
: система типов унифицирована, и вы получаете какую-либо ценность или ссылочную семантику, о которой вы просите.
С кодом, который вы написали
A c = B()
вы сказали компилятору создать новое значение типа B
, а затем преобразовать его в значение типа A
, сохранив значение в c
. Преобразование в этом случае означает получение данных A
из нового экземпляра B
, который вы создали, и копирование его в новый экземпляр A
, хранящийся в c
.
Вместо этого вы можете сделать это:
B b;
A &c = b;
Это все еще создает значение B
, но теперь c
является ссылкой на A
, что означает, что c
теперь будет ссылаться на созданный вами экземпляр B
, а не на копию его A
часть.
Теперь это все равно создает B
как локальную переменную, а объект, хранящийся в B
, уничтожается, как только B
выходит за пределы области видимости. Если вы хотите что-то более настойчивое, вам нужно будет использовать указатели; например что-то вроде
shared_ptr<A> c = make_shared<B>();
c->foo();
Вы можете сделать что-то более "сырое", например
A *b = new B();
но это "немой" указатель; shared_ptr
умнее, и ваш объект будет уничтожен, когда ничего не ссылается на него. Если вы сделаете последнее, вам придется самому уничтожить себя, когда это необходимо (и испортить это - общий источник ошибок).
Ваше замешательство проистекает из решающего различия между Java и С++.
В Java, если вы пишете
MyClass var = whatever;
Ваша переменная var
является ссылкой на объект, возвращаемый whatever
. Однако в С++ этот синтаксис означает "создать новый объект типа MyClass
, передав результат выражения whatever
соответствующему конструктору и скопировав полученный объект в переменную var
.
В частности, ваш код создает новый объект типа A
, называемый c
, и передает его созданный по умолчанию объект типа B
в его конструктор копирования (потому что это единственный конструктор, который подходит). Поскольку вновь созданный объект имеет тип A
, не вызывается тип B
, очевидно A
метод foo
.
Если вы хотите иметь ссылку на объект, вы должны явно запросить его в С++, добавив &
к типу. Однако ссылка на непостоянные объекты не может быть привязана к временным. поэтому вам нужно явно объявить объект, к которому вы привязываетесь (или, альтернативно, использовать ссылку на объект const и исправить свои функции-члены foo
const
, так как они все равно не изменяют объект). Поэтому простейшая версия вашего кода, выполняющая то, что вы хотите, будет читать:
// your original definitions of A and B assumed here
B b; // The object of type B
A& c = b; // c is now a *reference* to b
int main() { c.foo(); } // calls B::foo() thanks to polymorphism
Однако лучшая версия была бы const-correct, а затем могла бы использоваться ваша оригинальная конструкция:
#include <iostream>
class A
{
public:
virtual void foo() const // note the additional const here!
{ std::cout << "foo" << std::endl; }
};
class B : public A
{
public:
void foo() const // and also const here
{ std::cout << "overridden foo" << std::endl; }
};
A const& c = B(); // Since we bind to a const reference,
// the lifetime of the temporary is extended to the
// lifetime of the reference
int main() { c.foo(); } //prints overridden foo
(обратите внимание, что я удалил using namespace std;
, потому что это плохо (и ваш код использовал явный std::
в любом случае, поэтому он просто лишний).
Обратите внимание, что ссылки на С++ по-прежнему отличаются от ссылок на Java тем, что они не могут быть переназначены; вместо этого любое присваивание переходит к базовому объекту. Например:
#include <iostream>
class A { public: virtual void foo() const { std::cout << "I'm an A\n"; } };
class B: public A { public: void foo() const { std::cout << "I'm a B\n"; } };
class C: public A { public: void foo() const { std::cout << "I'm a C\n"; } };
B b;
C c;
int main()
{
A& ref = b; // bind reference ref to object b
ref.foo(); // outputs "I'm a B"
ref = c; // does *not* re-bind the reference to c, but calls A::operator= (which in this case is a no-op)
ref.foo(); // again outputs "I'm a B"
}
Если вы хотите изменить объект, на который вы ссылаетесь, вам придется использовать указатели:
// definitions of A, B and C as above
int main()
{
A* prt = &b; // pointer ptr points to b
prt->foo(); // outputs "I'm a B"
prt = &c; // reassign ptr to point to c
prt->foo(); // outputs "I'm a C"
}
Интересующая строка - это (вместо этого используется синтаксис с синтаксисом):
A c = B{};
Важно отметить, что при объявлении этого способа c
ведет себя как значение.
Ваш код строит локальный A
с именем c
из экземпляра B
. Это называется slicing: любая часть этого B
, которая также не является частью A
, была "нарезана", оставив только A
.
Предоставление семантики ссылок на символы в С++ (называемое косвенным) требует различной нотации.
Например:
A &c = B{};
A d = B{}; // Casts the intermediate B instance to const A &,
// then copy-constructs an A
c.foo(); // calls B::foo because c points to a B through an A interface
d.foo(); // calls A::foo because d is only an instance of an A
Обратите внимание, что время жизни промежуточного B
, к которому точки c
автоматически расширяется до области c
. С другой стороны, второй промежуток B
разрушается после завершения построения d
.
В С++ ссылки являются неизменяемыми (они не могут быть изменены после инициализации). При использовании в выражении, как будто объект (значение), к которому они указывают, был использован вместо:
A &c = B{};
c = A{}; // Calls the compiler-provided A::operator = (const A &)
// (a virtual assignment operator with this signature
// was not defined).
// This DOES NOT change where c points.
Указатели, с другой стороны, могут быть изменены:
A a{};
B b{};
A *cptr = &b;
cptr->foo(); // calls B::foo
cptr = &a;
cptr->foo(); // calls A::foo
точно, что сказал orlp. вы также должны научиться использовать указатели (они забавны)
A* c = new B();
c->foo(); // overriden foo
delete c;