首页 > 编程语言 >C++运行期多态和编译期多态(以不同的模板参数调用不同的函数)

C++运行期多态和编译期多态(以不同的模板参数调用不同的函数)

时间:2023-12-05 17:25:55浏览次数:36  
标签:函数 多态 C++ 编译 编译器 shout 模板

在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态;在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态在编译期进行,因此称为编译期多态或静态多态。

<h1 "="">1 运行期多态

运行期多态的设计思想要归结到类继承体系的设计上去。对于有相关功能的对象集合,我们总希望能够抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),然后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。

运行期多态的实现依赖于虚函数机制。当某个类声明了虚函数时,告诉编译器在在编译时不得绑定(静态绑定也只能绑定一个),编译器将为该类对象安插一个虚函数表指针,并为类(基类和派生类)设置虚函数表,虚函数表中存放的是该类虚函数地址。同时,改写虚函数调用代码(函数指针调用)。运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。

#include
using namespace std;
class Animal
{
public :
	virtual void shout() = 0;
};
class Dog :public Animal
{
public:
	virtual void shout(){ cout << "汪汪!"<<endl; }="" };="" class="" cat="" :public="" animal="" {="" public:="" virtual="" void="" shout(){="" cout="" <<="" "喵喵~"<<endl;="" bird="" :="" public="" "叽喳!"<<endl;="" ①="" 编译器会为有virtual函数成员的类建立一个虚函数表="" int="" main()="" *="" animal;="" dog="" dog;="" animal-="">shout();//虚函数的调用会被编译器转换为对虚函数表的访问
	Cat cat;
	// ② 编译器会为每个新建对象添加一函数指针,指向虚函数表
	animal = &cat;
	animal->shout();//animal代表this指针,shout是虚函数
	// ③ 编译器会改写对象调用成员函数代码,改写成指针调用的形式
	Animal * anim3 = new Bird;
	anim3->shout();
	delete anim3;
	system("pause");
	return 0;
}
/*
汪汪!
喵喵~
叽喳!
*/

编译器会构建一张虚表( vtable ),每一个类都有自己独特的虚表。同时,在这个继承链上,编译器会为基类插入一个隐式的指针(一般是对象的首地址),指向虚表,称为__vptr。然后,子类继承父类时,会获得继承下来的__vptr,再根据自己的类的情况兼容(修改虚函数表里的值、发生偏移等。于是,当我们构建具体的类时,若是基类类型,__vptr就会指向父类的vtable,若是子类类型,__vptr就会指向子类的vtable。

C++运行期多态和编译期多态(以不同的模板参数调用不同的函数)

 

下面再看一个实例来了解虚函数表,及对象的函数指针是如何指向虚函数表以及调用虚函数代码是如何改写的?

#include 
using namespace std;
class A
{
public:
 A(int _a1 = 1) : a1(_a1) { }
 virtual void f() { cout << "A::f" << endl; }
 virtual void g() { cout << "A::g" << endl; }
 virtual void h() { cout << "A::h" << endl; }
 ~A() {}
private:
 int a1;
};
class C : public A
{
public:
 C(int _a1 = 1, int _c = 4) :A(_a1), c(_c) { }
 virtual void f() { cout << "C::f" << endl; }
 virtual void g() { cout << "C::g" << endl; }
 virtual void h() { cout << "C::h" << endl; }
private:
 int c;
};
// 通过访问类对象的前4字节(32位编译器)找到虚函数表。
// 虚函数表最后一项用的是0,代表虚函数表结束。
typedef void(*FUNC)(); //重定义函数指针,指向函数的指针
void PrintVTable(long* vTable) //访问虚函数表
{
 if (vTable == NULL)
 {
 return;
 }
 cout << "vtbl:" << vTable << endl;
 int i = 0;
 for (; vTable[i] != 0; ++i)
 {
 printf("function : %d :0X%x->", i, vTable[i]);
 FUNC f = (FUNC)vTable[i];
 f(); //访问虚函数
 }
 cout << endl;
}
void main()
{
	A a1;
	long *p = (long *)(*(long*)&a1);
	PrintVTable(p);
	C c;
	long *p2 = (long *)(*(long*)&c);
	PrintVTable(p2);
	system("pause");
}
/*
vtbl:00471048
function : 0 :0X40105a->A::f
function : 1 :0X4012c6->A::g
function : 2 :0X4010b9->A::h
vtbl:00471070
function : 0 :0X4010eb->C::f
function : 1 :0X4011d1->C::g
function : 2 :0X401280->C::h
*/
C++运行期多态和编译期多态(以不同的模板参数调用不同的函数)

 

<h1 "="">2 编译期多态

对模板参数而言,多态是通过模板具现化和函数重载解析实现的。以不同的模板参数具现化导致调用不同的函数,这就是所谓的编译期多态。

相比较于运行期多态,实现编译期多态的类之间并不需要成为一个继承体系,它们之间可以没有什么关系,但约束是它们都有相同的隐式接口。

#include 
using namespace std;
class Animal
{
public :
 void shout() { cout << "发出动物的叫声" << endl; };
};
class Dog
{
public:
	void shout(){ cout << "汪汪!"<<endl; }="" };="" class="" cat="" {="" public:="" void="" shout(){="" cout="" <<="" "喵喵~"<<endl;="" bird="" "叽喳!"<<endl;="" template="" <typename="" t="">
void animalShout(T & t)
{
 t.shout();
}
int main()
{
 Animal anim;
 Dog dog;
 Cat cat;
 Bird bird;
	
 animalShout(anim);
 animalShout(dog);
 animalShout(cat);
 animalShout(bird);
	
 getchar();getchar();
}
/*
发出动物的叫声
汪汪!
喵喵~
叽喳!
*/

在编译之前,函数模板中t.shout()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shout是哪个具体类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用。

<h1 "="">3 运行期多态与编译期多态优缺点分析

3.1 运行期多态优点

OO设计中重要的特性,对客观世界直觉认识。

能够处理同一个继承体系下的异质类集合。

3.2 运行期多态缺点

运行期间进行虚函数绑定,提高了程序运行开销。

庞大的类继承层次,对接口的修改易影响类继承层次。

由于虚函数在运行期在确定,所以编译器无法对虚函数进行优化。

虚表指针增大了对象体积,类也多了一张虚函数表,当然,这是理所应当值得付出的资源消耗,列为缺点有点勉强。

3.3 编译期多态优点

它带来了泛型编程的概念,使得C++拥有泛型编程与STL这样的强大武器。

在编译器完成多态,提高运行期效率。

具有很强的适配性与松耦合性,对于特殊类型可由模板偏特化、全特化来处理。

3.4 编译期多态缺点

程序可读性降低,代码调试带来困难。

无法实现模板的分离编译,当工程很大时,编译时间不可小觑。

无法处理异质对象集合。

标签:函数,多态,C++,编译,编译器,shout,模板
From: https://www.cnblogs.com/ruanjianzhishi/p/17877707.html

相关文章

  • C++11、C++14、C++17、C++20新特性总结(5万字详解)(转载)
    文章目录C++11是什么,C++11标准的由来C++auto类型推导完全攻略auto类型推导的语法和规则auto的高级用法auto的限制auto的应用使用auto定义迭代器auto用于泛型编程C++decltype类型推导完全攻略exp注意事项decltype推导规则decltype的实际应用汇总auto和......
  • c++ json的解析和QT中json的操作
    1.下载jsoncpp源码2.首先建议jsoncpp源码编译成动态库https://www.bilibili.com/video/BV1pb4y1W7ZZhttps://www.bilibili.com/video/BV1Ra4y1e7gL (1)用QT的Cmake工具 (2)用Visualstudio a.工具打开jsoncpp源码,在CMakeLists.txt右键->jsoncpp的CMak......
  • C/C++ 实现枚举网上邻居信息
    在Windows系统中,通过网络邻居可以方便地查看本地网络中的共享资源和计算机。通过使用WindowsAPI中的一些网络相关函数,我们可以实现枚举网络邻居信息的功能,获取连接到本地网络的其他计算机的相关信息。本文将介绍一个简单的C++程序,使用WindowsAPI枚举网络邻居信息,并获取对端名称......
  • 矩阵模板
    #include<bits/stdc++.h>usingnamespacestd;structMatrix{ usingi64=longlong; i64N; vector<vector<i64>>A; Matrix(){N=0;} Matrix(intn){ N=n; A.resize(N+1); for(inti=0;i<=N;i++) A[i].resize(N......
  • 小谈设计模式(11)—模板方法模式
    (小谈设计模式(11)—模板方法模式)主要对目前市面上常见的23种设计模式进行逐一分析和总结,希望有兴趣的小伙伴们可以看一下,会持续更新的。希望各位可以监督我,我们一起学习进步,加油,各位。模板方法模式这是一种行为型设计模式,用于定义算法的框架,将算法的具体实现延迟到子类中。角......
  • java基础语法-pageage-构造方法-继承-多态
    java中的包-package包:包中有很多的类,根据类的功能不同,我们可以创建不同的包。包的主要功能:包的主要功能:用于分类管理包的基本语法package包的路径路径与路径之间使用点隔开:package.fatherlujing.sonlujing在一个文件中,可以没有包,或者一个包。但是不能出现两个包。......
  • 3、利用初始化好的虚拟机当作模板,用于克隆
    摘自:https://blog.51cto.com/mfc001/6408226 利用初始化好的虚拟机当作模板,用于克隆第一步:先拷贝个虚拟机当作模板[root@ubuntimages]#virt-clone-orocky8-f/var/lib/libvirt/images/rocky8-template.qcow2-nrocky8-templateAllocating'rocky8-templat......
  • 4、虚拟机单机、集群的克隆、删除脚本(以初始化好的虚拟机为模板)
    摘自:https://blog.51cto.com/mfc001/6408229 虚拟机克隆、删除脚本[root@ubunt~]#catclone.sh#!/bin/bash##./etc/init.d/functions(如果是ubuntu,注释此行)Red="\e[1;31m"Purple="\e[1;35m"Green="\e[1;32m"Blue="\e[1;3......
  • c++回调函数
    最近用到了回调函数,距离上次使用至少隔了5年了,又重新熟悉了一下。  转自:https://blog.csdn.net/hua12134/article/details/88264250什么是函数指针函数指针就是指向函数的指针,指向某种特定的类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关,例如:boollength......
  • 多态
    多态为什么产生:学生是学生,学生也是人1.语法StudentPersonStuedntstudent=newStuedent(); Personperson=newPerson();2.接口和类都可以产生多态isa3.实际开发中,面向接口编程4.多态调用的时候,如果父类或者接口里面没有这种字段或者方法,......