Как вы иерархия класс, определенный в том, что класс B
является производным от класса A
. Поэтому, когда вызывается конструктор класса B's
, он должен сначала вызвать конструктор A's
, прежде чем он сможет быть сконструирован. Оба A
& B
имеют ту же самую точную виртуальную функцию или определение, называемое test()
.
С вашей первой реализацией f()
ваш шаблон будет выводить тип параметра во время его компиляции. Он ищет тип класса, в котором, когда вы вызываете функцию шаблона, вы говорите этой функции шаблона, ожидая тип class A
. Затем он будет использовать A::test()
для вызова тестовой функции. В своей основной функции перед вызовом f()
вы создаете указатель типа class A
динамически и помещаете его в кучу, но вы используете конструктор B
, который является производным типом A
. Это будет использовать конструктор B's
для вызова конструктора A's
. Функция шаблона ожидает тип A
, поэтому он вызывает в вашем коде a.test()
или A::test()
.
В вашей второй декларации или определении f()
ваши классы определены одинаково и ведут себя точно так же.На этот раз отличается ваша функция шаблона, которая будет разрешена во время компиляции, чтобы вывести ее тип параметра. В этой версии функции ожидается address of
класс типа A
, так как теперь он ожидает адрес памяти в отличие от самой фактической переменной, на этот раз при создании или вызове функции f()
, где он ожидает параметр типа T&
теперь использует возможности ссылок C++, и теперь он вызывает b.test()
.
Если вы хотите узнать, почему это происходит, используйте свое первое объявление f()
и установите точку останова, в которой у вас есть A* a = new B();
, и входите в код по строкам, не переходите, а включайте. Затем сделайте точный точный процесс со своей второй версией f()
, и вы увидите, что происходит.
Это не потому, что класс является или не переопределяет виртуальную функцию; это связано с тем, как работают шаблонные функции.
Теперь мой вопрос в том, почему вы хотите создать указатель для базового типа и установить для него новую память, вызвав его конструктор производного типа.
Обычно с полиморфизмом и абстрактными классами вы обычно создаете тип производного, но у вас может быть контейнер, содержащий указатели на базовый класс производных, в котором вы обычно динамически добавляете их. Например, скажем, что у вас есть базовый класс Automobile
, который является абстрактным; что вы не можете создать этот класс напрямую, потому что он конструктор защищен, где доступ к нему могут получить только производные типы. Теперь производным типом может быть Car
, Van
, Truck
, SUV
, Jeep
, MotorCycle
и в каком-либо другом классе или функции у вас может быть сохранен vector<shared_ptr<Automobile>>
. Таким образом, вы можете толкнуть умный указатель грузовика, автомобиля и фургона, все в один и тот же контейнер, динамически отбрасывая эти сконструированные объекты указателями на их базовый тип Automobile
, так как они все наследуют от них! Однако при работе с абстрактными типами необходимо использовать особую осторожность.
Заканчивать эту маленькую программу, чтобы увидеть, как полиморфизм работает (нет абстрактных типов здесь)
#include <conio.h>
#include <string>
#include <iostream>
#include <vector>
#include <memory>
class Base {
public:
Base() {}
virtual ~Base(){}
virtual void test() const { std::cout << "Base" << std::endl; }
};
class DerivedA : public Base {
public:
DerivedA() : Base() {}
virtual ~DerivedA() {}
virtual void test() const override { std::cout << "DerivedA" << std::endl; }
};
class DerivedB : public Base {
public:
DerivedB() : Base() {}
virtual ~DerivedB() {}
virtual void test() const override { std::cout << "DerivedB" << std::endl; }
};
template<class T>
void f(T t) {
t.test();
}
template<class T>
void g(T& t) {
t.test();
}
int main() {
DerivedA* a = new DerivedA();
//f<DerivedA>(*a);
//g<DerivedA>(*a);
DerivedB* b = new DerivedB();
//f<DerivedB>(*b);
//g<DerivedB>(*b);
std::vector<Base*> vBases;
vBases.push_back(a);
vBases.push_back(b);
for (unsigned i = 0; i < vBases.size(); ++i) {
if (i == 0) {
std::cout << "First Iteration: i = " << i << std::endl;
} else if (i == 1) {
std::cout << "Second Iteration: i = " << i << std::endl;
}
f<DerivedA>(*dynamic_cast<DerivedA*>(vBases[i]));
f<DerivedB>(*dynamic_cast<DerivedB*>(vBases[i]));
std::cout << std::endl;
g<DerivedA>(*static_cast<DerivedA*>(vBases[i]));
g<DerivedB>(*static_cast<DerivedB*>(vBases[i]));
std::cout << std::endl;
}
delete a; // You Forgot To Delete Your Dynamic Pointers - Memory Leak!
delete b;
std::cout << "Press any key to quit" << std::endl;
_getch();
return 0;
}
Выход
DerivedA
DerivedB
DerivedA
DerivedA
DerivedA
DerivedB
DerivedB
DerivedB
Может быть, это поможет вам понять, что происходит с ваши два разных типа шаблонов, используя dynamic_cast<>
для f<>()
вашей первой версии вашей функции шаблона и используя static_cast<>
для g<>()
для ваша вторая версия вашей функции шаблона, которая берет ссылку вместо копии экземпляра переменной.
Если вы помните, есть два элемента в этом векторе первый будучи DerivedA*
и второй является DerivedB*
и которые относятся к типу Base
и сохраняются как Base*
в векторе. Первые 4 строки out put - это работа, которая выполняется только для первого элемента нашего вектора! Последние 4 строки out put - это работа, которая выполняется только для второго элемента нашего вектора!
Наш первый хранится элемент с индексом 0
имеет DerivedA
типа хранится как Base*
и первым вызовом f<>()
мы динамически отбрасываемой его к DerivedA*
типа, и мы затем разыменования указателя.Второй вызов f<>()
мы делаем то же самое, за исключением того, что мы динамически применяем его к типу DerivedB*
и уважаем его. Итак, здесь этот первый сохраненный объект вызывает DerivedA::test()
, тогда он вызывает DerivedB::test()
с помощью динамического приведения.
Следующие две строки все еще работают над одним и тем же элементом, который является нашим DerivedA*
, хранящимся как Base*
по индексу 0
в нашем векторе. На этот раз мы теперь используем g<>()
ваш второй метод вашего шаблона функции, и вместо использования dynamic_cast<>
мы теперь используем static_cast<>
, так как g<>()
ожидает ссылки на объект, а не на переменную стека. Если вы заметили, что на этот раз ничто не отбрасывается от одного типа к другому, и наш шаблон функции всегда вызывает DerivedA::test()
, хотя при втором вызове этого метода мы говорим ему, чтобы он использовал его для типа <DerivedB>
.
В течение следующих 4-х строк вывода теперь мы работаем с нашим 2 хранящегося объектом в векторе с индексом 1
, но на этот раз наш сохраненный объект имеет DerivedB
типа хранятся как Base*
. На следующих двух строках мы имеем тот же результат, что и в первой итерации. На этот раз DerivedB
отправляется на DerivedA
для первого вызова f<>()
и остается его собственным типом для второго вызова f<>()
и для последних двух строк, поскольку вы можете видеть сохраненный объект с индексом 1
, который имеет тип DerivedB
, хранящийся как a Base*
не изменяется или не сбрасывается до DerivedA
при первом вызове g<>()
.