c++ - C++中的多态

标签 c++ polymorphism c++-faq

据我所知:

C ++提供了三种不同类型的多态性。


虚拟功能
函数名称重载
运算符重载


除了以上三种类型的多态性外,还存在其他类型的多态性:


运行
编译时间
临时多态性
参数多态性


我知道可以通过虚拟函数实现运行时多态
模板函数可以实现静态多态

但是对于其他两个


临时多态性
参数多态性
website says


临时多态性:

如果可以使用的实际类型的范围是有限的,并且必须在使用前单独指定组合,则这称为临时多态性。

参数多态性:

如果编写的所有代码都没有提及任何特定类型,因此可以与任何数量的新类型透明地使用,则称为参数多态。

我很难理解他们:(

任何人都可以通过示例解释它们吗?
我希望这些问题的答案对他们大学的许多新学员有帮助。

最佳答案

了解/对多态性的要求

要理解多态性(在计算机科学中使用该术语),它有助于从对其进行简单的测试和定义开始。考虑:

    Type1 x;
    Type2 y;

    f(x);
    f(y);


在这里,f()将执行一些操作,并被赋予值xy作为输入。


  为了展现多态性,f()必须能够使用至少两种不同类型(例如intdouble)的值进行操作,找到并执行不同类型的适用代码。




C ++多态性机制

程序员指定的明确多态性

您可以编写f()使其可以以下列任何一种方式对多种类型进行操作:


预处理:

#define f(X) ((X) += 2)
// (note: in real code, use a longer uppercase name for a macro!)

重载:

void f(int& x)    { x += 2; }

void f(double& x) { x += 2; }

范本:

template <typename T>
void f(T& x) { x += 2; }

虚拟派遣:

struct Base { virtual Base& operator+=(int) = 0; };

struct X : Base
{
    X(int n) : n_(n) { }
    X& operator+=(int n) { n_ += n; return *this; }
    int n_;
};

struct Y : Base
{
    Y(double n) : n_(n) { }
    Y& operator+=(int n) { n_ += n; return *this; }
    double n_;
};

void f(Base& x) { x += 2; } // run-time polymorphic dispatch



其他相关机制

编译器为内置类型,标准转换和强制转换/强制提供的多态性将在后面讨论,以确保完整性:


无论如何,他们通常都会被直观地理解(保证有“哦,那个”反应),
它们影响了上述机制的要求和无缝使用的门槛,并且
解释是对更重要概念的轻率干扰。


术语

进一步分类

鉴于以上所述的多态机制,我们可以通过多种方式对它们进行分类:


什么时候选择了多态类型专用代码?


运行时意味着编译器必须为程序在运行时可能处理的所有类型生成代码,并在运行时选择正确的代码(虚拟分派)
编译时间是指在编译过程中选择特定于类型的代码。结果的后果:说一个仅在上面使用f参数调用int的程序-根据所使用的多态机制和内联选择,编译器可能会避免为f(double)生成任何代码,或者生成的代码可能会被丢弃指向编译或链接。 (除虚拟调度外,以上所有机制)


支持哪些类型?


即席的意思是您提供支持每种类型的显式代码(例如,重载,模板专门化);您明确添加了支持“为此”(按照临时含义)的类型,其他一些“此”,也可能是“那个” ;-)。
参数化意味着您可以尝试将函数用于各种参数类型,而无需做任何专门工作以使其支持它们(例如,模板,宏)。具有功能类似于模板/宏期望1的功能/运算符的对象就是模板/宏完成其工作所需的全部内容,而确切的类型无关紧要。从C ++ 11切入的“概念”有助于表达和实施这种期望-希望他们将其纳入以后的标准中。


参数多态性提供了鸭子的输入方式-归因于James Whitcomb Riley的一个概念,他显然说过:“当我看到一只鸟像鸭子走路,像鸭子一样游泳,像鸭子一样嘎嘎叫时,我称那只鸟为鸭子。”

template <typename Duck>
void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }

do_ducky_stuff(Vilified_Cygnet());


子类型(又名包含)多态性允许您在不更新算法/功能的情况下处理新类型,但是它们必须派生自相同的基类(虚拟调度)



1-模板非常灵活。 SFINAE(另请参见std::enable_if)有效地允许了对参数多态性的几组期望。例如,您可能会编码为:当您正在处理的数据类型具有.size()成员时,您将使用一个函数,否则将使用另一个不需要.size()的函数(但可能会受到某种影响-例如,使用较慢的strlen()或在日志中没有显示有用的消息)。您还可以在使用特定参数实例化模板时指定临时行为,保留某些参数为参数(partial template specialisation)或不为参数(full specialisation)。

“多态的”

Alf Steinbach评论说,在C ++ Standard中,多态仅指使用虚拟调度的运行时多态。通用电脑科学根据C ++创建者Bjarne Stroustrup的词汇表(http://www.stroustrup.com/glossary.html),含义更具有包容性:


  多态性-为不同类型的实体提供单一接口。虚函数通过基类提供的接口提供动态(运行时)多态性。重载的函数和模板提供了静态(编译时)多态性。 TC ++ PL 12.2.6、13.6.1,D&E 2.9。


这个答案-像问题一样-将C ++功能与Comp相关。科学术语。

讨论区

在C ++标准中,使用的“多态性”定义比Comp窄。科学社区,为确保观众之间的相互理解,请考虑...


使用明确的术语(“我们可以使该代码可用于其他类型吗?”或“我们可以使用虚拟分派吗?”而不是“我们可以使该代码多态吗?”),和/或
明确定义您的术语。


尽管如此,对于成为一名出色的C ++程序员来说,至关重要的是要了解多态真正为您服务的事情。

让您一次编写“算法”代码,然后将其应用于多种类型的数据

...然后非常了解不同的多态机制如何满足您的实际需求。

运行时多态适合:


由工厂方法处理的输入,并作为通过Base*处理的异构对象集合吐出来,
在运行时根据配置文件,命令行开关,UI设置等选择的实现,
实现在运行时会有所不同,例如针对状态机模式。


如果没有明确的运行时多态性驱动程序,则通常首选编译时选项。考虑:


模板类的所谓编译方面胜于在运行时失败的胖接口
SFINAE
CRTP
优化(许多内容包括内联代码和无效代码消除,循环展开,基于静态堆栈的数组与堆)
__FILE____LINE__,字符串文字串联和宏的其他独特功能(仍然是邪恶的;-))
模板和宏测试是否支持语义用法,但不要人为地限制提供支持的方式(因为虚拟分派往往要求完全匹配的成员函数覆盖)


其他支持多态的机制

如所承诺的,为了完整起见,涵盖了几个外围主题:


编译器提供的重载
转换
演员/强迫


该答案以对以上内容如何结合以赋能和简化多态代码(尤其是参数多态(模板和宏))的讨论结束。

映射到特定于类型的操作的机制

>隐式编译器提供的重载

从概念上讲,编译器会为内置类型重载许多运算符。从概念上讲,它与用户指定的重载没有区别,但由于容易被忽略而被列出。例如,您可以使用相同的符号int将它们添加到doublex += 2中,编译器将生成:


类型特定的CPU指令
相同类型的结果。


然后,重载无缝扩展到用户定义的类型:

std::string x;
int y = 0;

x += 'c';
y += 'c';


编译器为基本类型提供的重载在高级(3GL +)计算机语言中很常见,对多态性的明确讨论通常意味着更多。 (2GL(汇编语言)通常要求程序员针对不同的类型显式使用不同的助记符。)

>标准转换

C ++标准的第四部分介绍了标准转换。

第一点很好地总结了(从一个旧的草案起-希望仍然是正确的):


  -1-标准转换是为内置类型定义的隐式转换。条款conv列举了此类转换的全部内容。标准转换顺序是按以下顺序进行的标准转换的顺序:



下列一组中的零或一转换:左值到右值转换,数组到指针转换和函数到指针转换。
以下集合中的零个或一个转换:积分提升,浮点提升,积分转换,浮点转换,浮点积分转换,指针转换,成员转换指针和布尔转换。
零或一资格转换。



  [请注意:标准转换顺序可以为空,即可以不包含任何转换。 ]如果有必要将标准转换序列应用于表达式,以将其转换为所需的目标类型。


这些转换允许使用以下代码:

double a(double x) { return x + 2; }

a(3.14);
a(42);


应用先前的测试:


  要实现多态,[a()]必须能够使用至少两种不同类型(例如intdouble)的值进行操作,查找并执行适合类型的代码。


a()本身专门为double运行代码,因此不是多态的。

但是,在第二次调用a()时,编译器知道会为“浮点升级”(标准§4)生成适合类型的代码,以将42转换为42.0。额外的代码在调用函数中。我们将在结论中讨论其重要性。

>强制,强制转换,隐式构造函数

这些机制允许用户定义的类指定类似于内置类型的标准转换的行为。我们来看一下:

int a, b;

if (std::cin >> a >> b)
    f(a, b);


在此,借助于转换运算符在布尔上下文中评估对象std::cin。可以在概念上与上述主题中的“标准促销”中的“积分促销”等进行分组。

隐式构造函数可以有效地执行相同的操作,但是受强制转换类型控制:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`


编译器提供的重载,转换和强制的含义

考虑:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}


如果我们希望量x在除法过程中被视为实数(即6.5,而不是四舍五入为6),则只需更改为typedef double Amount

很好,但是使代码明确“类型正确”并不是一件费力的事情:

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }


但是,请考虑我们可以将第一个版本转换为template

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}


由于这些小的“便利功能”,因此可以很容易地为intdouble实例化它并按预期工作。没有这些功能,我们将需要显式强制转换,类型特征和/或策略类,以及一些冗长且容易出错的混乱,例如:

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}


因此,编译器为内置类型,标准转换,强制转换/强制/隐式构造函数提供的运算符重载-它们都为多态性提供了微妙的支持。在此答案顶部的定义中,它们通过映射解决了“查找和执行适合类型的代码”的问题:


远离参数类型


来自许多数据类型的多态算法代码句柄
为(可能更少)数量(相同或其他)类型编写的代码。

从常量类型的值“到”参数类型


它们不会自行建立多态上下文,但会帮助授权/简化此类上下文中的代码。

您可能会觉得受骗……看起来似乎不多。重要性在于,在参数多态上下文中(即,在模板或宏内部),我们试图支持任意大范围的类型,但通常希望针对其他功能,字面量和为小套的类型。当操作/值在逻辑上相同时,它减少了按类型创建几乎相同的函数或数据的需求。这些功能相互配合,以增强“尽力而为”的态度,通过使用有限的可用功能和数据来实现直观上的预期,并且只有在真正含糊不清的情况下才出现错误。

这有助于限制对支持多态代码的多态代码的需求,在多态的使用周围画一个更紧密的网,因此局部使用不会强制广泛使用,并根据需要提供多态的好处,而不必承担必须公开实现的成本。编译时,在目标代码中具有相同逻辑功能的多个副本以支持使用的类型,并且在进行虚拟分派时与内联或至少在编译时解析调用相反。正如C ++中的典型做法一样,程序员拥有很大的自由度来控制使用多态性的边界。

关于c++ - C++中的多态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29202560/

相关文章:

polymorphism - Laravel 4 : save a polymorphic relationship's record: undefined method newQuery()

c++ - 什么是 "Argument-Dependent Lookup"(又名 ADL,或 "Koenig Lookup")?

c++ - 将值传递给功能模板的最佳方法

c++ - gcc 的扩展初始化程序列出了警告

c++ - 包含指向多态类的指针的 vector

c++ - 什么是模板演绎指南,我们应该在什么时候使用它们?

c++ - 为什么我必须通过this指针访问模板基类成员?

c++ - 父类中 protected 数据在子类中不可用?

c++ - Vulkan 存储缓冲区与图像采样器

c# - 具有多态数组的 WCF 客户端调用方法失败