多态问题

Rainstorey 2008-04-14 04:07:52
都说虚函数的调用建立在指针和引用的基础之上.但是为什么呢?

class A
{
public:
virtual void test()
{
cout<<"A"<<endl;
}

};
class B:public A
{
void test()
{
cout<<"B"<<endl;
}

};

void main()
{
A a;
B b;
a=b;
a.test();//"A"


}
//////////////////////////////////////////////////////////
如有知道的希望把原理讲完整,不要什么内存布局,vftable爆出一二个词就完了.把整个过程以及为什么可以为什么不可以都说清楚了才能理解事物的本质原理.
...全文
165 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
Rainstorey 2008-04-14
  • 打赏
  • 举报
回复
突然顿悟:
A a;//this指向a的数据区
B b;//this指向b的数据区
a=b;//this还是a的数据区
*a=&b;//a对象的this指针变成了指向b的数据区.
这似乎就是多态的核心实质.至于编译生成的vftable等等细节问题只是辅助实现多态的机制.本质上其实是多态函数中的this指针其实是指向其子类的虚函数(查找虚函数表的结果)...
我终于明白了其中"奥妙".但表达出来呵呵可能就不会了..
我不是一语言爱好着,但是我是一细节探索者.万事皆有源头,万物皆有归宗.感谢大家!!
晨星 2008-04-14
  • 打赏
  • 举报
回复
都说得差不多了,连参考书目都有了,偶就不补充了。
大家鼓掌吧。
liveforme 2008-04-14
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 Rainstorey 的回复:]
5楼,问题是为什么要必须,理由是什么,推导又是什么?
[/Quote]

1.没虚函数就没有虚函数表,那就不可能有方法选择。
2.没继承关系,虚函数表里就不会有你想要的方法
3.多态就是让基类的指针或引用来操作子类的对象,基类用自己的对象或对象指针操作自己,那有什么意义
独孤过儿 2008-04-14
  • 打赏
  • 举报
回复
现在Vehicle用作模板参数而非公共基类对象(事实上,这里的Vehicle只是一个符合直觉的记号而已,此外别无它意)。经过编译器处理后,我们最终会得到run_vehicle<Car>()和 run_vehicle<Airplane>()两个不同的函数。这和动态多态不同,动态多态凭借虚函数分派机制在运行期只有一个 run_vehicle()函数。
我们无法再透明地处理异质对象集合了,因为所有类型都必须在编译期予以决定。不过,为不同的vehicles引入不同的集合只是举手之劳。由于无需再将集合元素局限于指针或引用,我们现在可以从执行性能和类型安全两方面获得好处:

// static_poly_2.cpp

#include <iostream>
#include <vector>
#include "static_poly.h"

// run同质vehicles集合
template <typename Vehicle>
void run_vehicles(const std::vector<Vehicle>& vehicles)
{
for (unsigned int i = 0; i < vehicles.size(); ++i)
{
vehicles[i].run(); // 根据vehicle的具体类型调用相应的run()
}
}

int main()
{
Car car1, car2;
Airplane airplane1, airplane2;

std::vector<Car> vc; // 同质cars集合
vc.push_back(car1);
vc.push_back(car2);
//vc.push_back(airplane1); // 错误:类型不匹配
run_vehicles(vc); // run cars

std::vector<Airplane> vs; // 同质airplanes集合
vs.push_back(airplane1);
vs.push_back(airplane2);
//vs.push_back(car1); // 错误:类型不匹配
run_vehicles(vs); // run airplanes
}

两种多态机制的结合使用

在一些高级C++应用中,我们可能需要结合使用动态多态和静态多态两种机制,以期达到对象操作的优雅、安全和高效。例如,我们既希望一致而优雅地处理vehicles的 run问题,又希望“安全而高效”地完成给飞行器(飞机、飞艇等)进行“空中加油”这样的高难度动作。为此,我们首先将上面的vehicles类层次结构改写如下:

// dscombine_poly.h

#include <iostream>
#include <vector>

// 公共抽象基类Vehicle
class Vehicle
{
public:
virtual void run() const = 0;
};

// 派生于Vehicle的具体类Car
class Car: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a car\n";
}
};

// 派生于Vehicle的具体类Airplane
class Airplane: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a airplane\n";
}

void add_oil() const
{
std::cout << "add oil to airplane\n";
}
};

// 派生于Vehicle的具体类Airship
class Airship: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a airship\n";
}

void add_oil() const
{
std::cout << "add oil to airship\n";
}
};

我们理想中的应用程序可以编写如下:

// dscombine_poly.cpp

#include <iostream>
#include <vector>
#include "dscombine_poly.h"

// run异质vehicles集合
void run_vehicles(const std::vector<Vehicle*>& vehicles)
{
for (unsigned int i = 0; i < vehicles.size(); ++i)
{
vehicles[i]->run(); // 根据具体的vehicle类型调用对应的run()
}
}

// 为某种特定的aircrafts同质对象集合进行“空中加油”
template <typename Aircraft>
void add_oil_to_aircrafts_in_the_sky(const std::vector<Aircraft>& aircrafts)
{
for (unsigned int i = 0; i < aircrafts.size(); ++i)
{
aircrafts[i].add_oil();
}
}

int main()
{
Car car1, car2;
Airplane airplane1, airplane2;

Airship airship1, airship2;
std::vector<Vehicle*> v; // 异质vehicles集合
v.push_back(&car1);
v.push_back(&airplane1);
v.push_back(&airship1);
run_vehicles(v); // run不同种类的vehicles

std::vector<Airplane> vp; // 同质airplanes集合
vp.push_back(airplane1);
vp.push_back(airplane2);
add_oil_to_aircrafts_in_the_sky(vp); // 为airplanes进行“空中加油”

std::vector<Airship> vs; // 同质airships集合
vs.push_back(airship1);
vs.push_back(airship2);
add_oil_to_aircrafts_in_the_sky(vs); // 为airships进行“空中加油”
}

我们保留了类层次结构,目的是为了能够利用run_vehicles()一致而优雅地处理异质对象集合vehicles的run问题。同时,利用函数模板 add_oil_to_aircrafts_in_the_sky<Aircraft>(),我们仍然可以处理特定种类的vehicles — aircrafts(包括airplanes和airships)的“空中加油”问题。其中,我们避开使用指针,从而在执行性能和类型安全两方面达到了预期目标。

结语

长期以来,C++社群对于多态的内涵和外延一直争论不休。在comp.object这样的网络论坛上,此类话题争论至今仍随处可见。曾经有人将动态多态(dynamic polymorphism)称为inclusion polymorphism,而将静态多态(static polymorphism)称为parametric polymorphism或parameterized polymorphism。

我注重到2003年斯坦福大学公开的一份C++ and Object-Oriented Programming教案中明确提到了函数多态概念:Function overloading is also referred to as function polymorphism as it involves one function having many forms。文后的“参考文献”单元给出了这个网页链接。

可能你是第一次看到宏多态(macro polymorphism)这个术语。不必讶异 — 也许我就是造出这个术语的“第一人”。显然,带变量的宏(或类似于函数的宏或伪函数宏)的替换机制除了免除小型函数的调用开销之外,也表现出了类似的多态性。在我们上面的例子中,字符串相加所表现出来的符合直觉的连接操作,事实上是由底部运算符重载机制(operator overloading)支持的。值得指出的是,C++社群中有人将运算符重载所表现出来的多态称为ad hoc polymorphism。

David Vandevoorde和Nicolai M. Josuttis在他们的著作C++ Templates: The Complete Guide一书中系统地阐述了静态多态和动态多态技术。因为认为“和其他语言机制关系不大”,这本书没有提及“宏多态”(以及“函数多态”)。(需要说明的是,笔者本人是这本书的繁体中文版译者之一,本文正是基于这本书的第14章The Polymorphic Power of Templates编写而成)

动态多态只需要一个多态函数,生成的可执行代码尺寸较小,静态多态必须针对不同的类型产生不同的模板实体,尺寸会大一些,但生成的代码会更快,因为无需通过指针进行间接操作。静态多态比动态多态更加类型安全,因为全部绑定都被检查于编译期。正如前面例子所示,你不可将一个错误的类型的对象插入到从一个模板实例化而来的容器之中。此外,正如你已经看到的那样,动态多态可以优雅地处理异质对象集合,而静态多态可以用来实现安全、高效的同质对象集合操作。

静态多态为C++带来了泛型编程(generic programming)的概念。泛型编程可以认为是“组件功能基于框架整体而设计”的模板编程。STL就是泛型编程的一个典范。STL是一个框架,它提供了大量的算法、容器和迭代器,全部以模板技术实现。从理论上讲,STL的功能当然可以使用动态多态来实现,不过这样一来其性能必将大打折扣。

静态多态还为C++社群带来了泛型模式(generic patterns)的概念。理论上,每一个需要通过虚函数和类继续而支持的设计模式都可以利用基于模板的静态多态技术(甚至可以结合使用动态多态和静态多态两种技术)而实现。正如你看到的那样,Andrei Alexandrescu的天才作品Modern C++ Design: Generic Programming and Design Patterns Applied(Addison-Wesley)和Loki程序库已经走在了我们的前面。

参考文献
1. David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide, Addison Wesley, 2002.
2. Chris Neumann, CS193d (Summer 2003) C++ and Object-Oriented Programming, http://www.stanford.edu/class/cs193d/, 2003.
独孤过儿 2008-04-14
  • 打赏
  • 举报
回复
每次讨论到多态问题的时候,为了掩饰我自己表达上的缺陷,我通常都会搬出这篇文章来...
=======================================================================
=======================================================================

C++多态技术
摘要

本文描述了C++中的各种多态性。重点阐述了面向对象的动态多态和基于模板的静态多态,并初步探讨了两种技术的结合使用。

要害词

多态 继续 虚函数 模板 宏 函数重载 泛型编程 泛型模式

导言

多态(polymorphism)一词最初来源于希腊语polumorphos,含义是具有多种形式或形态的情形。在程序设计领域,一个广泛认可的定义是“一种将不同的非凡行为和单个泛化记号相关联的能力”。和纯粹的面向对象程序设计语言不同,C++中的多态有着更广泛的含义。除了常见的通过类继续和虚函数机制生效于运行期的动态多态(dynamic polymorphism)外,模板也答应将不同的非凡行为和单个泛化记号相关联,由于这种关联处理于编译期而非运行期,因此被称为静态多态(static polymorphism)。
事实上,带变量的宏和函数重载机制也答应将不同的非凡行为和单个泛化记号相关联。然而,习惯上我们并不将它们展现出来的行为称为多态(或静态多态)。今天,当我们谈及多态时,假如没有明确所指,默认就是动态多态,而静态多态则是指基于模板的多态。不过,在这篇以C++各种多态技术为主题的文章中,我们首先还是回顾一下C++社群争论已久的另一种“多态”:函数多态(function polymorphism),以及更不常提的“宏多态(macro polymorphism)”。

函数多态

也就是我们常说的函数重载(function overloading)。基于不同的参数列表,同一个函数名字可以指向不同的函数定义:

// overload_poly.cpp

#include <iostream>
#include <string>

// 定义两个重载函数

int my_add(int a, int b)
{
return a + b;
}

int my_add(int a, std::string b)
{
return a + atoi(b.c_str());
}

int main()
{
int i = my_add(1, 2); // 两个整数相加
int s = my_add(1, "2"); // 一个整数和一个字符串相加
std::cout << "i = " << i << "\n";
std::cout << "s = " << s << "\n";
}

根据参数列表的不同(类型、个数或兼而有之),my_add(1, 2)和my_add(1, "2")被分别编译为对my_add(int, int)和my_add(int, std::string)的调用。实现原理在于编译器根据不同的参数列表对同名函数进行名字重整,而后这些同名函数就变成了彼此不同的函数。比方说,也许某个编译器会将my_add()函数名字分别重整为my_add_int_int()和my_add_int_str()。

宏多态

带变量的宏可以实现一种初级形式的静态多态:
// macro_poly.cpp

#include <iostream>
#include <string>

// 定义泛化记号:宏ADD
#define ADD(A, B) (A) + (B);

int main()
{
int i1(1), i2(2);
std::string s1("Hello, "), s2("world!");
int i = ADD(i1, i2); // 两个整数相加
std::string s = ADD(s1, s2); // 两个字符串“相加”
std::cout << "i = " << i << "\n";
std::cout << "s = " << s << "\n";
}
当程序被编译时,表达式ADD(i1, i2)和ADD(s1, s2)分别被替换为两个整数相加和两个字符串相加的具体表达式。整数相加体现为求和,而字符串相加则体现为连接。程序的输出结果符合直觉:
1 + 2 = 3
Hello, + world! = Hello, world!

动态多态

这就是众所周知的的多态。现代面向对象语言对这个概念的定义是一致的。其技术基础在于继续机制和虚函数。例如,我们可以定义一个抽象基类Vehicle和两个派生于Vehicle的具体类Car和Airplane:

// dynamic_poly.h

#include <iostream>

// 公共抽象基类Vehicle
class Vehicle
{
public:
virtual void run() const = 0;
};

// 派生于Vehicle的具体类Car
class Car: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a car\n";
}
};

// 派生于Vehicle的具体类Airplane
class Airplane: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a airplane\n";
}
};
客户程序可以通过指向基类Vehicle的指针(或引用)来操纵具体对象。通过指向基类对象的指针(或引用)来调用一个虚函数,会导致对被指向的具体对象之相应成员的调用:

// dynamic_poly_1.cpp

#include <iostream>
#include <vector>
#include "dynamic_poly.h"

// 通过指针run任何vehicle
void run_vehicle(const Vehicle* vehicle)
{
vehicle->run(); // 根据vehicle的具体类型调用对应的run()
}

int main()
{
Car car;
Airplane airplane;
run_vehicle(&car); // 调用Car::run()
run_vehicle(&airplane); // 调用Airplane::run()
}

此例中,要害的多态接口元素为虚函数run()。由于run_vehicle()的参数为指向基类Vehicle的指针,因而无法在编译期决定使用哪一个版本的run()。在运行期,为了分派函数调用,虚函数被调用的那个对象的完整动态类型将被访问。这样一来,对一个Car对象调用 run_vehicle(),实际上将调用Car::run(),而对于Airplane对象而言将调用Airplane::run()。
或许动态多态最吸引人之处在于处理异质对象集合的能力:

// dynamic_poly_2.cpp

#include <iostream>
#include <vector>
#include "dynamic_poly.h"

// run异质vehicles集合
void run_vehicles(const std::vector<Vehicle*>& vehicles)
{
for (unsigned int i = 0; i < vehicles.size(); ++i)
{
vehicles[i]->run(); // 根据具体vehicle的类型调用对应的run()
}
}

int main()
{
Car car;
Airplane airplane;
std::vector<Vehicle*> v; // 异质vehicles集合
v.push_back(&car);
v.push_back(&airplane);
run_vehicles(v); // run不同类型的vehicles
}
在run_vehicles()中,vehicles[i]->run()依据正被迭代的元素的类型而调用不同的成员函数。这从一个侧面体现了面向对象编程风格的优雅。

静态多态

假如说动态多态是通过虚函数来表达共同接口的话,那么静态多态则是通过“彼此单独定义但支持共同操作的具体类”来表达共同性,换句话说,必须存在必需的同名成员函数。
我们可以采用静态多态机制重写上一节的例子。这一次,我们不再定义vehicles类层次结构,相反,我们编写彼此无关的具体类Car和Airplane(它们都有一个run()成员函数):

// static_poly.h

#include <iostream>

//具体类Car
class Car
{
public:
void run() const
{
std::cout << "run a car\n";
}
};

//具体类Airplane
class Airplane
{
public:
void run() const
{
std::cout << "run a airplane\n";
}
};

run_vehicle()应用程序被改写如下:

// static_poly_1.cpp

#include <iostream>
#include <vector>
#include "static_poly.h"

// 通过引用而run任何vehicle
template <typename Vehicle>
void run_vehicle(const Vehicle& vehicle)
{
vehicle.run(); // 根据vehicle的具体类型调用对应的run()
}

int main()
{
Car car;
Airplane airplane;
run_vehicle(car); // 调用Car::run()
run_vehicle(airplane); // 调用Airplane::run()
}
就呆在云上 2008-04-14
  • 打赏
  • 举报
回复
一句话,如果不使用指针或者引用的话,而是直接的赋值“=”就会发生对象的切片,切片!懂否?
不懂去看c++编程思想,或者google一下也可以
baihacker 2008-04-14
  • 打赏
  • 举报
回复
如果不是*或者&
调用test的实际类型肯定是在编译时确定的!!!,既然调用者的类型确定了,还去多态什么?
Rainstorey 2008-04-14
  • 打赏
  • 举报
回复
4楼并没有清除说明要vftable发生作用就必须用*或&做为对象标识.
5楼,问题是为什么要必须,理由是什么,推导又是什么?
zhxx_qs 2008-04-14
  • 打赏
  • 举报
回复
以父类的指针指向子类的实例, 如果调用的函数是虚函数, 并且子类已经改写了它, 那么调用的一定是真正创建的类型的函数, 而不是指针指向类型定义的虚函数
独孤过儿 2008-04-14
  • 打赏
  • 举报
回复
多态有几个前提条件:

1、基类需要有虚函数

2、必须要用继承关系

3、必须用基类的指针或引用来操作子类的对象

满足上面的三个条件,才有可能有多态的发生...
baihacker 2008-04-14
  • 打赏
  • 举报
回复
1.B是A的子类型,所以A的指针可以指向B的一个实例.(引用同理)
2.这个指针调用test方法,如果是静态绑定,由于是A的指针就认为指向的是A的实例而去调用A::test
如果是动态绑定,认为A的指针可能指向了一个A的派生类的实例,那么怎么确定具体test方法呢?
只有把test的地址放在这个实例里,在调用test的时候去查询地址,然后再调用,这样就可以根据不同的实例调用不同的test了.(查询方法,把所有的虚函数的地址放在一起,根据出现的顺序做成一个表,比如这里是test那么就在表中查找第1个地址并认为这个地址就是test的地址)
不同类型的实例存储的这个表不同,所以能根据实际指向的类型调用不同的函数.
c_spark 2008-04-14
  • 打赏
  • 举报
回复
class A 
{
public:
virtual void test()
{
cout <<"A" <<endl;
}

};
class B:public A
{
void test()
{
cout <<"B" <<endl;
}

};

void main()
{
A *a;
B b;
a=&b; //使用多态时,使用指针或引用,这样父类才能真正指向子类的地址
a->test();//"B"
}
taodm 2008-04-14
  • 打赏
  • 举报
回复
去看《深度探索C++对象模型》
baihacker 2008-04-14
  • 打赏
  • 举报
回复

#include <iostream>
using namespace std;
class A
{
public:
virtual void test()
{
cout <<"A" <<endl;
}

};
class B:public A
{
void test()
{
cout <<"B" <<endl;
}

};

void main()
{
B b;
A &a = b;
A *pa = &b;
a.test();
pa->test();

return 0;
}


64,282

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧