你如何定义你的类层次结构是 classB
是从 class 派生的A
。因此,当调用类B's
构造函数时,必须先调用A's
构造函数才能构造它。A
&都有B
相同的虚函数或定义,称为test()
.
在您第一次实现f()
模板时,将在编译时推断出参数类型。它正在寻找一个类类型,当您调用模板函数时,您在 main 中告诉该模板函数期望类型为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>>
存储的。因此,您可以通过将这些构造的对象指针动态转换为它们的 Base 类型,将卡车、汽车和货车的智能指针全部推送到同一个容器中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
也许这将帮助您了解您的两种不同模板类型发生了什么,方法是将 adynamic_cast<>
用于f<>()
您的模板函数的第一个版本,并将static_cast<>
forg<>()
用于您的模板函数的第二个版本,它采用引用而不是变量的堆栈副本.
如果你还记得这个向量中有两个元素,第一个是 a DerivedA*
,第二个是 a DerivedB*
,它们都是一个类型Base
,并作为 a 存储Base*
在向量中。输出的前 4 行是仅在向量的第一个元素上完成的工作!输出的最后 4 行是仅在向量的第二个元素上完成的工作!
我们在 index 处存储的第一个元素0
是DerivedA
存储为 a 的类型Base*
,第一次调用f<>()
我们将它动态地转换为一个DerivedA*
类型,然后我们取消引用指针。第二次调用f<>()
我们做同样的事情,只是我们动态地将它转换为一个DerivedB*
类型并尊重它。所以这里第一个存储的对象正在调用DerivedA::test()
,然后它DerivedB::test()
通过使用动态转换进行调用。
接下来的两行仍在处理同一个元素,该元素DerivedA*
存储为向量中的Base*
at 索引0
。这一次我们现在使用g<>()
你的函数模板的第二种方法,而不是使用 adynamic_cast<>
我们现在使用 astatic_cast<>
因为g<>()
期望引用一个对象而不是堆栈变量本身。如果您注意到这次没有任何东西从一种类型转换为另一种类型,并且我们的函数模板总是在调用DerivedA::test()
,即使在第二次调用此方法时我们告诉它将其转换为一种<DerivedB>
类型。
对于接下来的 4 行输出,我们现在正在使用索引处向量中的第二个存储对象,1
但这次我们保存的对象的DerivedB
类型存储为Base*
. 在接下来的两行中,我们的输出与第一次迭代中的输出相同。这一次DerivedB
被强制转换为 aDerivedA
以进行第一次调用,f<>()
并在第二次调用和最后两行中保持其自己的类型,f<>()
因为您可以看到索引处的存储对象1
的DerivedB
类型存储为 aBase*
没有被更改或在第一次DerivedA
调用g<>()
.