你如何定义你的类层次是类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行是在我们矢量的第一个元素上完成的工作!输出的最后4行是在我们矢量的第二个元素上完成的工作!
我们首先在索引0
存储元件是存储为Base*
一个DerivedA
类型和f<>()
第一呼叫我们动态地将其转换为DerivedA*
类型的,并且我们然后取消引用指针。第二次调用f<>()
我们做同样的事情,除了我们动态地将它转换为DerivedB*
类型并且尊重它。因此,这里第一个存储的对象调用DerivedA::test()
,然后它使用动态强制转换调用DerivedB::test()
。
接下来的两行仍在处理相同的元素,这是我们的DerivedA*
存储为Base*
的索引0
在我们的向量中。这一次,我们现在使用g<>()
您的函数模板的第二种方法,而不是使用dynamic_cast<>
我们现在使用static_cast<>
,因为g<>()
期望引用一个对象而不是一个堆栈变量本身。如果你注意到这一次没有任何东西从一种类型转换到另一种类型,我们的函数模板总是调用DerivedA::test()
,即使在第二次调用这个方法时,我们告诉它将它转换为<DerivedB>
类型。
在接下来的4行输出我们正与我们的载体第二存储对象的指数1
工作,但这次我们保存的对象是存储为Base*
一个DerivedB
类型。在接下来的两行中,我们具有与第一次迭代相同的输出。这次DerivedB
正在铸造到DerivedA
为f<>()
的第一个电话,并保持它自己的类型的第二个电话f<>()
和最后两行,因为你可以看到存储的对象索引1
是DerivedB
类型存储为在g<>()
的第一个呼叫中,Base*
未被更改或转换为DerivedA
类型。