C++中的多态
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5854581/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Polymorphism in C++
提问by Vijay
AFAIK:
AFAIK:
C++ provides three different types of polymorphism.
C++ 提供了三种不同类型的多态性。
- Virtual functions
- Function name overloading
- Operator overloading
- 虚函数
- 函数名重载
- 运算符重载
In addition to the above three types of polymorphism, there exist other kinds of polymorphism:
除了上述三种多态性外,还存在其他类型的多态性:
- run-time
- compile-time
- ad-hoc polymorphism
- parametric polymorphism
- 运行
- 编译时
- 特设多态性
- 参数多态性
I know that runtime polymorphismcan be achieved by virtual functionsand static polymorphismcan be achieved by template functions
我知道运行时多态可以通过虚函数实现 ,静态多态可以通过模板函数实现
But for the other two
但是对于另外两个
- ad-hoc polymorphism
- parametric polymorphism the website says,
- 特设多态性
- 网站上说的参数多态性,
ad-hoc polymorphism:
特设多态性:
If the range of actual types that can be used is finite and the combinations must be individually specified prior to use, this is called ad-hoc polymorphism.
如果可以使用的实际类型的范围是有限的,并且在使用之前必须单独指定组合,这称为临时多态性。
parametric polymorphism:
参数多态性:
If all code is written without mention of any specific type and thus can be used transparently with any number of new types it is called parametric polymorphism.
如果所有代码的编写都没有提及任何特定类型,因此可以透明地与任意数量的新类型一起使用,则称为参数多态性。
I can hardly understand them :(
我几乎无法理解他们:(
can anyone explain them both if possible with an example? I hope the answers to this questions would be helpful for many new passouts from their colleges.
如果可能的话,谁能用一个例子来解释它们?我希望这些问题的答案对他们大学的许多新毕业生有所帮助。
回答by Tony Delroy
Understanding of / requirements for polymorphism
对多态性的理解/要求
To understand polymorphism - as the term is used in Computing Science - it helps to start from a simple test for and definition of it. Consider:
要理解多态性 - 正如计算科学中使用的术语 - 从对它的简单测试和定义开始是有帮助的。考虑:
Type1 x;
Type2 y;
f(x);
f(y);
Here, f()
is to perform some operation and is being given values x
and y
as inputs.
这里,f()
是执行一些操作并被赋予值x
并y
作为输入。
To exhibit polymorphism,
f()
must be able to operate with values of at least two distincttypes (e.g.int
anddouble
), finding and executing distinct type-appropriate code.
要表现出多态性,
f()
必须能够使用至少两种不同类型(例如int
和double
)的值进行操作,找到并执行不同类型的代码。
C++ mechanisms for polymorphism
C++ 多态机制
Explicit programmer-specified polymorphism
显式程序员指定的多态性
You can write f()
such that it can operate on multiple types in any of the following ways:
您可以编写f()
使其可以通过以下任何一种方式对多种类型进行操作:
Preprocessing:
#define f(X) ((X) += 2) // (note: in real code, use a longer uppercase name for a macro!)
Overloading:
void f(int& x) { x += 2; } void f(double& x) { x += 2; }
Templates:
template <typename T> void f(T& x) { x += 2; }
Virtual dispatch:
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
预处理:
#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
Other related mechanisms
其他相关机制
Compiler-provided polymorphism for builtin types, Standard conversions, and casting/coercion are discussed later for completeness as:
编译器为内建类型提供的多态性、标准转换和强制转换/强制转换将在后面讨论,以确保完整性:
- they're commonly intuitively understood anyway (warranting a "oh, that" reaction),
- they impact the threshold in requiring, and seamlessness in using, the above mechanisms, and
- explanation is a fiddly distraction from more important concepts.
- 无论如何,它们通常都可以直观地理解(保证“哦,那个”反应),
- 它们会影响要求和无缝使用上述机制的门槛,以及
- 解释是对更重要概念的巧妙干扰。
Terminology
术语
Further categorisation
进一步分类
Given the polymorphic mechanisms above, we can categorise them in various ways:
鉴于上述多态机制,我们可以通过多种方式对它们进行分类:
When is the polymorphic type-specific code selected?
- Run timemeans the compiler must generate code for all the types the program might handle while running, and at run-time the correct code is selected (virtual dispatch)
- Compile timemeans the choice of type-specific code is made during compilation. A consequence of this: say a program only called
f
above withint
arguments - depending on the polymorphic mechanism used and inlining choices the compiler might avoid generating any code forf(double)
, or generated code might be thrown away at some point in compilation or linking. (all mechanisms above except virtual dispatch)
Which types are supported?
- Ad-hocmeaning you provide explicit code to support each type (e.g. overloading, template specialisation); you explicitly add support "for this" (as per ad hoc's meaning) type, some other "this", and maybe "that" too ;-).
Parametricmeaning you can just try to use the function for various parameter types without specifically doing anything to enable its support for them (e.g. templates, macros). An object with functions/operators that act like the template/macro expects1isall that template/macro needs to do its job, with the exact type being irrelevant. The "concepts" introduced by C++20 express and enforce such expectations - see cppreferencepage here.
Parametric polymorphism provides duck typing- a concept attributed to James Whitcomb Riley who apparently said "When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.".
template <typename Duck> void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); } do_ducky_stuff(Vilified_Cygnet());
Subtype (aka inclusion) polymorphismallows you to work on new types without updating the algorithm/function, but they must be derived from the same base class (virtual dispatch)
什么时候选择多态类型特定代码?
- 运行时意味着编译器必须为程序在运行时可能处理的所有类型生成代码,并在运行时选择正确的代码(虚拟分派)
- 编译时间意味着在编译期间选择特定于类型的代码。这样做的结果是:假设一个程序只
f
在上面使用int
参数调用 - 根据所使用的多态机制和内联选择,编译器可能会避免为 生成任何代码f(double)
,或者生成的代码可能会在编译或链接的某个时刻被丢弃。(上述所有机制,除了虚拟调度)
支持哪些类型?
- Ad-hoc意味着您提供明确的代码来支持每种类型(例如重载、模板专业化);您明确添加了“为此”(根据ad hoc的含义)类型、其他一些“这个”,也许还有“那个”的支持;-)。
参数意味着您可以尝试将函数用于各种参数类型,而无需专门做任何事情来启用对它们的支持(例如模板、宏)。与功能/经营者的物体,像模板/宏期望行为1是所有的模板/宏需要做的工作,具有确切类型是不相关的。C++20 引入的“概念”表达并强制执行这样的期望 - 请参阅此处的cppreference页面。
参数多态性提供了鸭子类型——这个概念归因于 James Whitcomb Riley,他显然说过“当我看到一只鸟像鸭子一样走路,像鸭子一样游泳,像鸭子一样嘎嘎叫时,我称那只鸟为鸭子。” .
template <typename Duck> void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); } do_ducky_stuff(Vilified_Cygnet());
子类型(又名包含)多态性允许您在不更新算法/函数的情况下处理新类型,但它们必须派生自相同的基类(虚拟调度)
1- Templates are extremely flexible. SFINAE(see also std::enable_if
) effectively allows several sets of expectations for parametric polymorphism. For example, you might encode that when the type of data you're processing has a .size()
member you'll use one function, otherwise another function that doesn't need .size()
(but presumably suffers in some way - e.g. using the slower strlen()
or not printing as useful a message in the log). You can also specify ad-hoc behaviours when the template is instantiated with specific parameters, either leaving some parameters parametric (partial template specialisation) or not (full specialisation).
1- 模板非常灵活。 SFINAE(另请参阅参考资料std::enable_if
)有效地允许对参数多态性进行多组期望。例如,您可能会编码,当您正在处理的数据类型有一个.size()
成员时,您将使用一个函数,否则另一个不需要的函数.size()
(但可能会以某种方式受到影响 - 例如使用较慢的strlen()
或不打印为有用的日志中的消息)。您还可以在使用特定参数实例化模板时指定临时行为,或者保留一些参数参数化(部分模板特化)或不保留(完全特化)。
"Polymorphic"
“多态”
Alf Steinbach comments that in the C++ Standard polymorphiconly refers to run-time polymorphism using virtual dispatch. General Comp. Sci. meaning is more inclusive, as per C++ creator Bjarne Stroustrup's glossary (http://www.stroustrup.com/glossary.html):
Alf Steinbach 评论说,在 C++ 标准中,多态仅指使用虚拟分派的运行时多态。一般比较 科学。根据 C++ 创建者 Bjarne Stroustrup 的词汇表 ( http://www.stroustrup.com/glossary.html) ,含义更具包容性:
polymorphism - providing a single interface to entities of different types. Virtual functions provide dynamic (run-time) polymorphism through an interface provided by a base class. Overloaded functions and templates provide static (compile-time) polymorphism. TC++PL 12.2.6, 13.6.1, D&E 2.9.
多态性 - 为不同类型的实体提供单一接口。虚函数通过基类提供的接口提供动态(运行时)多态性。重载函数和模板提供静态(编译时)多态性。TC++PL 12.2.6、13.6.1、D&E 2.9。
This answer - like the question - relates C++ features to the Comp. Sci. terminology.
这个答案 - 就像问题一样 - 将 C++ 特性与 Comp. 科学。术语。
Discussion
讨论
With the C++ Standard using a narrower definition of "polymorphism" than the Comp. Sci. community, to ensure mutual understanding for youraudience consider...
使用比 Comp 更窄的“多态性”定义的 C++ 标准。科学。社区,以确保您的听众相互理解,请考虑...
- using unambiguous terminology ("can we make this code reusable for other types?" or "can we use virtual dispatch?" rather than "can we make this code polymorphic?"), and/or
- clearly defining your terminology.
- 使用明确的术语(“我们可以使这段代码可重用于其他类型吗?”或“我们可以使用虚拟分派吗?”而不是“我们可以使这段代码多态吗?”),和/或
- 明确定义您的术语。
Still, what's crucial to being a great C++ programmer is understandingwhat polymorphism's really doing for you...
尽管如此,成为一名出色的 C++ 程序员的关键是了解多态性真正为您做什么......
letting you write "algorithmic" code once and then apply it to many types of data
让您编写一次“算法”代码,然后将其应用于多种类型的数据
...and then be very aware of how different polymorphic mechanisms match your actual needs.
...然后非常了解不同的多态机制如何满足您的实际需求。
Run-time polymorphism suits:
运行时多态适合:
- input processed by factory methods and spat out as an heterogeneous object collection handled via
Base*
s, - implementation chosen at runtime based on config files, command line switches, UI settings etc.,
- implementation varied at runtime, such as for a state machine pattern.
- 输入由工厂方法处理并作为通过
Base*
s处理的异构对象集合吐出, - 在运行时根据配置文件、命令行开关、UI 设置等选择的实现,
- 实现在运行时有所不同,例如状态机模式。
When there's not a clear driver for run-time polymorphism, compile-time options are often preferable. Consider:
当运行时多态性没有明确的驱动程序时,编译时选项通常是可取的。考虑:
- the compile-what's-called aspect of templated classes is preferable to fat interfaces failing at runtime
- SFINAE
- CRTP
- optimisations (many including inlining and dead code elimination, loop unrolling, static stack-based arrays vs heap)
__FILE__
,__LINE__
, string literal concatenation and other unique capabilities of macros (which remain evil ;-))- templates and macros test semantic usage is supported, but don't artificially restrict how that support is provided (as virtual dispatch tends to by requiring exactly matching member function overrides)
- 模板化类的所谓编译方面比在运行时失败的胖接口更可取
- 上海外国语学院
- CRTP
- 优化(许多包括内联和死代码消除、循环展开、基于静态堆栈的数组与堆)
__FILE__
,__LINE__
, 字符串文字连接和宏的其他独特功能(仍然是邪恶的;-))- 支持模板和宏测试语义使用,但不要人为限制提供支持的方式(因为虚拟分派往往需要完全匹配的成员函数覆盖)
Other mechanisms supporting polymorphism
支持多态性的其他机制
As promised, for completeness several peripheral topics are covered:
正如所承诺的,为了完整性,涵盖了几个外围主题:
- compiler-provided overloads
- conversions
- casts/coercion
- 编译器提供的重载
- 转换
- 强制转换/强制
This answer concludes with a discussion of how the above combine to empower and simplify polymorphic code - especially parametric polymorphism (templates and macros).
这个答案最后讨论了上述内容如何结合以增强和简化多态代码 - 特别是参数多态性(模板和宏)。
Mechanisms for mapping to type-specific operations
映射到特定类型操作的机制
> Implicit compiler-provided overloads
> 隐式编译器提供的重载
Conceptually, the compiler overloadsmany operators for builtin types. It's not conceptually different from user-specified overloading, but is listed as it's easily overlooked. For example, you can add to int
s and double
s using the same notation x += 2
and the compiler produces:
从概念上讲,编译器为内置类型重载了许多运算符。它在概念上与用户指定的重载没有区别,但被列出是因为它很容易被忽视。例如,您可以使用相同的符号添加到int
s 和double
sx += 2
并且编译器生成:
- type-specific CPU instructions
- a result of the same type.
- 特定于类型的 CPU 指令
- 相同类型的结果。
Overloading then seamlessly extends to user-defined types:
重载然后无缝扩展到用户定义的类型:
std::string x;
int y = 0;
x += 'c';
y += 'c';
Compiler-provided overloads for basic types is common in high-level (3GL+) computer languages, and explicit discussion of polymorphism generally implies something more. (2GLs - assembly languages - often require the programmer to explicitly use different mnemonics for different types.)
编译器提供的基本类型重载在高级 (3GL+) 计算机语言中很常见,对多态性的明确讨论通常意味着更多。(2GLs - 汇编语言 - 通常要求程序员为不同的类型明确使用不同的助记符。)
> Standard conversions
> 标准转换
The C++ Standard's fourth section describes Standard conversions.
C++ 标准的第四部分描述了标准转换。
The first point summarises nicely (from an old draft - hopefully still substantially correct):
第一点总结得很好(来自旧草案 - 希望仍然基本正确):
-1- Standard conversions are implicit conversions defined for built-in types. Clause conv enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:
-1- 标准转换是为内置类型定义的隐式转换。子句 conv 列举了完整的此类转换集。标准转换序列是按以下顺序进行的标准转换序列:
Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.
Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.
Zero or one qualification conversion.
来自以下集合的零个或一个转换:左值到右值的转换、数组到指针的转换和函数到指针的转换。
来自以下集合的零个或一个转换:整数提升、浮点提升、整数转换、浮点转换、浮点整数转换、指针转换、成员指针转换和布尔转换。
零个或一个资格转换。
[Note: a standard conversion sequence can be empty, i.e., it can consist of no conversions. ] A standard conversion sequence will be applied to an expression if necessary to convert it to a required destination type.
[注意:标准转换序列可以为空,即它可以不包含任何转换。] 如有必要,标准转换序列将应用于表达式以将其转换为所需的目标类型。
These conversions allow code such as:
这些转换允许以下代码:
double a(double x) { return x + 2; }
a(3.14);
a(42);
Applying the earlier test:
应用较早的测试:
To be polymorphic, [
a()
] must be able to operate with values of at least two distincttypes (e.g.int
anddouble
), finding and executing type-appropriate code.
要成为多态, [
a()
] 必须能够对至少两种不同类型(例如int
和double
)的值进行操作,找到并执行适合类型的代码。
a()
itself runs code specifically for double
and is therefore notpolymorphic.
a()
本身专门运行代码double
,因此不是多态的。
But, in the second call to a()
the compiler knows to generate type-appropriate code for a "floating point promotion" (Standard §4) to convert 42
to 42.0
. That extra code is in the callingfunction. We'll discuss the significance of this in the conclusion.
但是,在对a()
编译器的第二次调用中,知道为“浮点提升”(标准第 4 节)生成适合类型的代码以转换42
为42.0
. 额外的代码在调用函数中。我们将在结论中讨论这一点的重要性。
> Coercion, casts, implicit constructors
> 强制、强制转换、隐式构造函数
These mechanisms allow user-defined classes to specify behaviours akin to builtin types' Standard conversions. Let's have a look:
这些机制允许用户定义的类指定类似于内置类型的标准转换的行为。我们来看一下:
int a, b;
if (std::cin >> a >> b)
f(a, b);
Here, the object std::cin
is evaluated in a boolean context, with the help of a conversion operator. This can be conceptually grouped with "integral promotions" et al from the Standard conversions in the topic above.
在这里,在std::cin
转换运算符的帮助下,在布尔上下文中评估对象。这可以在概念上与上述主题中的标准转换中的“整体促销”等归为一组。
Implicit constructors effectively do the same thing, but are controlled by the cast-to type:
隐式构造函数有效地做同样的事情,但由强制转换类型控制:
f(const std::string& x);
f("hello"); // invokes `std::string::string(const char*)`
Implications of compiler-provided overloads, conversions and coercion
编译器提供的重载、转换和强制的含义
Consider:
考虑:
void f()
{
typedef int Amount;
Amount x = 13;
x /= 2;
std::cout << x * 1.1;
}
If we want the amount x
to be treated as a real number during the division (i.e. be 6.5 rather than rounded down to 6), we onlyneed change to typedef double Amount
.
如果我们希望x
在除法过程中将数量视为实数(即 6.5 而不是四舍五入到 6),我们只需要更改为typedef double Amount
。
That's nice, but it wouldn't have been toomuch work to make the code explicitly "type correct":
这很好,但是让代码明确地“类型正确”不会有太多工作:
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;
} }
But, consider that we can transform the first version into a template
:
但是,考虑到我们可以将第一个版本转换为template
:
template <typename Amount>
void f()
{
Amount x = 13;
x /= 2;
std::cout << x * 1.1;
}
It's due to those little "convenience features" that it can be so easily instantiated for either int
or double
and work as intended. Without these features, we'd need explicit casts, type traits and/or policy classes, some verbose, error-prone mess like:
正是由于那些小小的“便利功能”,它可以很容易地实例化为int
或double
并按预期工作。如果没有这些功能,我们将需要显式转换、类型特征和/或策略类,以及一些冗长、容易出错的混乱,例如:
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;
}
So, compiler-provided operator overloading for builtin types, Standard conversions, casting / coercion / implicit constructors - they all contribute subtle support for polymorphism. From the definition at the top of this answer, they address "finding and executing type-appropriate code" by mapping:
因此,编译器为内置类型提供运算符重载、标准转换、强制转换/强制/隐式构造函数——它们都为多态提供了微妙的支持。根据此答案顶部的定义,他们通过映射解决了“查找和执行适合类型的代码”问题:
"away" from parameter types
fromthe many data types polymorphic algorithmic code handles
tocode written for a (potentially lesser) number of (the same or other) types.
"to" parametric types from values of constant type
“远离”参数类型
来自多种数据类型的多态算法代码句柄
到为(相同或其他)类型的(潜在较小)数目编写的代码。
从常量类型的值“到”参数类型
They do notestablish polymorphic contexts by themselves, but do help empower/simplify code inside such contexts.
它们本身不会建立多态上下文,但确实有助于授权/简化此类上下文中的代码。
You may feel cheated... it doesn't seem like much. The significance is that in parametric polymorphic contexts (i.e. inside templates or macros), we're trying to support an arbitrarily large range of types but often want to express operations on them in terms of other functions, literals and operations that were designed for a small set of types. It reduces the need to create near-identical functions or data on a per-type basis when the operation/value is logically the same. These features cooperate to add an attitude of "best effort", doing what's intuitively expected by using the limited available functions and data and only stopping with an error when there's real ambiguity.
你可能会觉得被骗了……这似乎并不多。重要的是,在参数多态上下文中(即在模板或宏中),我们试图支持任意大范围的类型,但通常希望根据其他函数、文字和操作来表达对它们的操作,这些操作专为一小部分类型。当操作/值在逻辑上相同时,它减少了在每个类型的基础上创建几乎相同的功能或数据的需要。这些功能相互配合,增加了“尽力而为”的态度,通过使用有限的可用功能和数据来做直觉上预期的事情,并且只有在真正有歧义时才停止错误。
This helps limit the need for polymorphic code supporting polymorphic code, drawing a tighter net around the use of polymorphism so localised use doesn't force widespread use, and making the benefits of polymorphism available as needed without imposing the costs of having to expose implementation at compile time, have multiple copies of the same logical function in the object code to support the used types, and in doing virtual dispatch as opposed to inlining or at least compile-time resolved calls. As is typical in C++, the programmer is given a lot of freedom to control the boundaries within which polymorphism is used.
这有助于限制对支持多态代码的多态代码的需求,围绕多态的使用绘制更紧密的网络,因此本地化使用不会强制广泛使用,并根据需要使多态的好处可用,而无需增加必须在以下位置公开实现的成本编译时,在目标代码中具有相同逻辑函数的多个副本以支持所使用的类型,并在执行虚拟调度而不是内联或至少编译时解析调用。正如在 C++ 中的典型情况一样,程序员有很大的自由来控制使用多态的边界。
回答by Steve314
In C++, the important distinction is run-time vs. compile-time binding. Ad-hoc vs. parametric doesn't really help, as I'll explain later.
在 C++ 中,重要的区别是运行时绑定与编译时绑定。Ad-hoc 与参数化并没有真正的帮助,我稍后会解释。
|----------------------+--------------|
| Form | Resolved at |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates | compile-time |
| virtual methods | run-time |
|----------------------+--------------|
Note - run-time polymorphism may still be resolved at compile-time, but that's just optimization. Needing to support run-time resolution efficiently, and trading off against other issues, is part of what led to virtual functions being what they are. And that's really key for all forms of polymorphism in C++ - each arises from different sets of trade-offs made in a different context.
注意 - 运行时多态性可能仍会在编译时解决,但这只是优化。需要有效地支持运行时解决方案,并权衡其他问题,是导致虚拟功能成为现实的部分原因。这对于 C++ 中所有形式的多态性来说真的很关键——每一种都来自在不同上下文中进行的不同权衡集。
Function overloading and operator overloading are the same thing in every way that matters. The names and the syntax for using them doesn't affect polymorphism.
函数重载和运算符重载在任何重要的方面都是一回事。名称和使用它们的语法不会影响多态性。
Templates allow you to specify lots of function overloads at once.
模板允许您一次指定大量函数重载。
There's another set of names for the same resolution-time idea...
对于相同的解决时间想法,还有另一组名称......
|---------------+--------------|
| early binding | compile-time |
| late binding | run-time |
|---------------+--------------|
These names are more associated with OOP, so it's a bit odd to say that a template or other non-member function uses early binding.
这些名称更多地与 OOP 相关联,因此说模板或其他非成员函数使用早期绑定有点奇怪。
To better understand the relationship between virtual functions and function overloading, it's also useful to understand the difference between "single dispatch" and "multiple dispatch". The idea can be understood as a progression...
为了更好地理解虚函数和函数重载之间的关系,理解“单分派”和“多分派”之间的区别也很有用。这个想法可以理解为一个进步......
- First, there are monomorphic functions. The implementation of the function is uniquely identified by the function name. None of the parameters is special.
- Then, there is single dispatch. One of the parameters is considered special, and used (along with the name) to identify which implementation to use. In OOP, we tend to think of this parameter as "the object", list it before the function name etc.
- Then, there is multiple dispatch. Any/all parameters contribute to identifying which implementation to use. Therefore, once again, none of the parameters needs to be special.
- 首先,有单态函数。函数的实现由函数名唯一标识。没有一个参数是特殊的。
- 然后,有一个单一的调度。其中一个参数被认为是特殊的,用于(与名称一起)标识要使用的实现。在 OOP 中,我们倾向于将此参数视为“对象”,将其列在函数名称等之前。
- 然后,有多个调度。任何/所有参数都有助于确定要使用的实现。因此,再一次,没有一个参数需要特殊。
There's obviously more to OOP than an excuse to nominate one parameter as special, but that is one part of it. And relating back to what I said about trade-offs - single dispatch is quite easy to do efficiently (the usual implementation is called "virtual tables"). Multiple dispatch is more awkward, not just in terms of efficiency, but also for separate compilation. If you're curious, you might look up "the expression problem".
显然,OOP 不仅仅是将一个参数指定为特殊参数的借口,但这只是其中的一部分。回到我所说的权衡 - 单分派很容易有效地进行(通常的实现称为“虚拟表”)。多分派比较笨拙,不仅在效率方面,而且对于单独编译也是如此。如果你很好奇,你可能会查找“表达问题”。
Just as it's a bit odd to use the term "early binding" for non-member functions, it's a bit odd to use the terms "single dispatch" and "multiple dispatch" where polymorphism is resolved at compile-time. Usually, C++ is considered not to have multiple dispatch, which is considered a particular kind of run-time resolution. However, function overloading can be seen as multiple-dispatch done at compile-time.
正如对非成员函数使用术语“早期绑定”有点奇怪一样,使用术语“单分派”和“多分派”也有点奇怪,其中多态是在编译时解决的。通常,C++ 被认为没有多重分派,这被认为是一种特殊的运行时解决方案。但是,函数重载可以看作是在编译时完成的多次分派。
Getting back to parametric vs. ad-hoc polymorphism, these terms are more popular in functional programming, and they don't quite work in C++. Even so...
回到参数多态与临时多态,这些术语在函数式编程中更流行,但它们在 C++ 中不太适用。即便如此...
Parametric polymorphism means that you have types as parameters, and the exact same code is used irrespective of what type you use for those parameters.
参数多态意味着您将类型作为参数,并且无论您为这些参数使用什么类型,都使用完全相同的代码。
Ad-hoc polymorphism is ad-hoc in the sense that you provide different code depending on the particular types.
特设多态性是特设的,因为您根据特定类型提供不同的代码。
Overloading and virtual functions are both examples of ad-hoc polymorphism.
重载和虚函数都是即席多态的例子。
Again, there's some synonyms...
还有一些同义词...
|------------+---------------|
| parametric | unconstrained |
| ad-hoc | constrained |
|------------+---------------|
Except these aren't quite synonyms, though they're commonly treated as though they were, and that's where confusion is likely to arise in C++.
除了这些不是同义词之外,尽管它们通常被视为同义词,但这就是 C++ 中可能出现混淆的地方。
The reasoning behind treating these as synonyms is that by constraining polymorphism to particular classes of types, it becomes possible to use operations specific to those classes of types. The word "classes" here can be interpreted in the OOP sense, but really just refers to (usually named) sets of types that share certain operations.
将这些视为同义词的原因是,通过将多态性限制为特定类型的类型,可以使用特定于这些类型类型的操作。这里的“类”一词可以在 OOP 意义上进行解释,但实际上只是指(通常命名的)共享某些操作的类型集。
So parametric polymorphism is usually taken (at least by default) to imply unconstrained polymorphism. Because the same code is used irrespective of the type parameters, the only supportable operations are those that work for all types. By leaving the set of types unconstrained, you severely limit the set of operations you can apply to those types.
因此,参数多态性通常被认为(至少默认情况下)意味着不受约束的多态性。因为无论类型参数如何都使用相同的代码,所以唯一可支持的操作是适用于所有类型的操作。通过让类型集不受约束,您会严重限制可以应用于这些类型的操作集。
In e.g. Haskell, you can have...
在例如 Haskell 中,您可以拥有...
myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y
The a
here is an unconstrained polymorphic type. It could be anything, so there's not much we can do with values of that type.
在a
这里是一个不受约束的多态型。它可以是任何东西,所以我们对这种类型的值无能为力。
myfunc2 :: Num a => a -> a
myfunc2 x = x + 3
Here, a
is constrained to be a member of the Num
class - types that act like numbers. That constraint allows you to do number-ish things with those values, such as add them. Even the 3
is polymorphic - type inference figures out that you mean the 3
of type a
.
在这里,a
被限制为Num
类的成员- 像数字一样的类型。该约束允许您使用这些值执行数字式操作,例如添加它们。甚至3
is 多态类型推断也表明您指的是3
of 类型a
。
I think of this as constrained parametric polymorphism. There's only one implementation, but it can only be applied in constrained cases. The ad-hoc aspect is the choice of which +
and 3
to use. Each "instance" of Num
has it's own distinct implementation of these. So even in Haskell "parametric" and "unconstrained" aren't really synonyms - don't blame me, it's not my fault!
我认为这是受约束的参数多态性。只有一种实现,但它只能应用于受约束的情况。临时方面是选择哪个+
和3
使用哪个。每个“实例”Num
都有自己独特的这些实现。所以即使在 Haskell 中,“参数化”和“无约束”也不是真正的同义词——别怪我,这不是我的错!
In C++, both overloading and virtual functions are ad-hoc polymorphism. The definition of ad-hoc polymorphism doesn't care whether the implementation is selected at run-time or compile-time.
在 C++ 中,重载和虚函数都是即席多态。ad-hoc 多态性的定义并不关心是在运行时还是编译时选择实现。
C++ gets very close to parametric polymorphism with templates if every template parameter has type typename
. There are type parameters, and there's a single implementation no matter which types are used. However, the "Substitution Failure Is Not An Error" rule means that implicit constraints arise as a result of using operations within the template. Additional complications include template specialization for providing alternative templates - different (ad-hoc) implementations.
如果每个模板参数都具有 type ,则 C++ 非常接近模板的参数多态性typename
。有类型参数,无论使用哪种类型,都有一个实现。但是,“替换失败不是错误”规则意味着隐式约束是由于在模板中使用操作而产生的。其他复杂情况包括用于提供替代模板的模板专业化 - 不同的(临时)实现。
So in a way C++ has parametric polymorphism, but it's implicitly constrained and could be overridden by ad-hoc alternatives - ie this classification doesn't really work for C++.
因此,在某种程度上,C++ 具有参数多态性,但它是隐式约束的,并且可能被临时替代方案覆盖——即这种分类并不真正适用于 C++。
回答by Eric Z
As to ad-hoc polymorphism, it means function overloading or operator overloading. Check out here:
对于ad-hoc多态性,它意味着函数重载或运算符重载。在这里查看:
http://en.wikipedia.org/wiki/Ad-hoc_polymorphism
http://en.wikipedia.org/wiki/Ad-hoc_polymorphism
As to parametric polymorphism, template functions can also be counted in because they don't necessarily take in parameters of FIXED types. For example, one function can sort array of integers and it can also sort array of strings, etc.
至于参数多态性,模板函数也可以算在内,因为它们不一定接受FIXED类型的参数。例如,一个函数可以对整数数组进行排序,也可以对字符串数组进行排序等。
回答by Joe
This may not be of any help, but I made this to introduce my friends to programming by giving out defined functions, like START
, and END
for the main function so it was not too daunting (they only used the main.cppfile). It contains Polymorphic classes and structs, templates, vectors, arrays, preproccessor directives, friendship, operators and pointers (all of which you should probably know before attempting polymorphism):
这可能没有任何帮助,但我这样做是为了向我的朋友介绍编程,通过给出定义的函数,如START
, 和END
main 函数,所以它不会太令人生畏(他们只使用main.cpp文件)。它包含多态类和结构、模板、向量、数组、预处理器指令、友谊、运算符和指针(在尝试多态之前,您可能应该知道所有这些):
Note: It is not finished, but you can get the idea
注意:还没有完成,但你可以得到这个想法
main.cpp
主程序
#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
Library MyLibrary;
Book MyBook("My Book", "Me");
MyBook.Summarize();
MyBook += "Hello World";
MyBook += "HI";
MyBook.EditAuthor("Joe");
MyBook.EditName("Hello Book");
MyBook.Summarize();
FixedBookCollection<FairyTale> FBooks("Fairytale Books");
FairyTale MyTale("Tale", "Joe");
FBooks += MyTale;
BookCollection E("E");
MyLibrary += E;
MyLibrary += FBooks;
MyLibrary.Summarize();
MyLibrary -= FBooks;
MyLibrary.Summarize();
FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
/* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
/* Extension Work */ Duplicate->Summarize();
END
main.h
主文件
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{
public:
ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
return new ClassToDuplicate(ToDuplicate);
}
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
void operator+=(const char* Page){
pages_.push_back(Page);
}
void EditAuthor(const char* AuthorName){
if(approve(AuthorName)){
author_ = AuthorName;
}
else{
std::ostringstream errorMessage;
errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
throw std::exception(errorMessage.str().c_str());
}
}
void EditName(const char* Name){
if(approve(Name)){
name_ = Name;
}
else{
std::ostringstream errorMessage;
errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
throw std::exception(errorMessage.str().c_str());
}
}
virtual void Summarize(){
std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
<< pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
if(pages_.size() > 0){
ListPages(std::cout);
}
}
private:
std::vector<const char*> pages_;
const char* name_;
const char* author_;
void ListPages(std::ostream& output){
for(int i = 0; i < pages_.size(); ++i){
output << pages_[i] << std::endl;
}
}
};
class FairyTale : public Book{
public:
FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
BookCollection(const char* Name) : name_(Name){}
virtual void operator+=(const Book& Book)try{
Collection.push_back(&Book);
}catch(const std::exception& e){
std::ostringstream errorMessage;
errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
throw std::exception(errorMessage.str().c_str());
}
virtual void operator-=(const Book& Book){
for(int i = 0; i < Collection.size(); ++i){
if(Collection[i] == &Book){
Collection.erase(Collection.begin() + i);
return;
}
}
std::ostringstream errorMessage;
errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
throw std::exception(errorMessage.str().c_str());
}
private:
const char* name_;
Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
FixedBookCollection(const char* Name) : BookCollection(Name){
if(!isBook<FixedType>()){
std::ostringstream errorMessage;
errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
throw std::exception(errorMessage.str().c_str());
delete this;
}
}
void operator+=(const FixedType& Book)try{
Collection.push_back(&Book);
}catch(const std::exception& e){
std::ostringstream errorMessage;
errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
throw std::exception(errorMessage.str().c_str());
}
void operator-=(const FixedType& Book){
for(int i = 0; i < Collection.size(); ++i){
if(Collection[i] == &Book){
Collection.erase(Collection.begin() + i);
return;
}
}
std::ostringstream errorMessage;
errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
throw std::exception(errorMessage.str().c_str());
}
private:
std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
void operator+=(const Book& Book)try{
if(currentPos + 1 > Size){
std::ostringstream errorMessage;
errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
throw std::exception(errorMessage.str().c_str());
}
this->at(currentPos++) = &Book;
}catch(const std::exception& e){
std::ostringstream errorMessage;
errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
throw std::exception(errorMessage.str().c_str());
}
private:
const char* name_;
int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
void operator+=(const BookCollection& Collection){
for(int i = 0; i < size(); ++i){
if((*this)[i] == &Collection){
std::ostringstream errorMessage;
errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
throw std::exception(errorMessage.str().c_str());
}
}
push_back(&Collection);
}
void operator-=(const BookCollection& Collection){
for(int i = 0; i < size(); ++i){
if((*this)[i] == &Collection){
erase(begin() + i);
return;
}
}
std::ostringstream errorMessage;
errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
throw std::exception(errorMessage.str().c_str());
}
Book* DuplicateBook(Book* Book)const{
return (Book->Duplicate(*Book));
}
void Summarize(){
std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
if(size() > 0){
for(int i = 0; i < size(); ++i){
std::cout << (*this)[i]->name_ << std::endl;
}
}
}
};
回答by user2976089
Here is a basic example using Polymorphic classes
这是一个使用多态类的基本示例
#include <iostream>
class Animal{
public:
Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
virtual void Speak(){
std::cout << "I am an animal called " << name_ << std::endl;
}
const char* name_;
};
class Dog : public Animal{
public:
Dog(const char* Name) : Animal(Name) {/*...*/}
void Speak(){
std::cout << "I am a dog called " << name_ << std::endl;
}
};
int main(void){
Animal Bob("Bob");
Dog Steve("Steve");
Bob.Speak();
Steve.Speak();
//return (0);
}
回答by Jayraj Srikriti Naidu
Polymorphism means many forms as such it is used for an operator to act differently under different instances. Polymorphism is used to implement inheritance. For ex, we have defined a fn draw () for a class shape then the draw fn can be implemented for drawing circle, box, triangle and other shapes. ( which are objects of the class shape)
多态性意味着许多形式,它用于操作符在不同实例下的不同行为。多态用于实现继承。例如,我们为类形状定义了一个 fn draw(),那么 draw fn 可以实现绘制圆形、方框、三角形等形状。(它们是类形状的对象)
回答by Sanchit
If anybody says CUT to these people
如果有人对这些人说 CUT
The Surgeon
The Hair Stylist
The Actor
What will happen?
会发生什么?
The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.
So above representation shows What is polymorphism (same name, different behavior) in OOP.
所以上面的表示显示了什么是 OOP 中的多态性(同名,不同的行为)。
If you are going for an interview and interviewer asks you tell/show a live example for polymorphism in the same room we are sitting at, say-
如果你要去面试,面试官要求你在我们坐在的同一个房间里讲述/展示一个多态性的现场例子,说 -
Answer - Door / Windows
答案 - 门/窗
Wondering How?
想知道如何?
Through Door / Window - a person can come, air can come, light can come, rain can come, etc.
通过门/窗——人可以来,空气可以来,光可以来,雨可以来等等。
i.e. One form different behavior(Polymorphism).
即一种形式不同的行为(多态)。
To understand it better and in a simple manner I used above example.. If you need reference for code follow above answers.
为了更好地以简单的方式理解它,我使用了上面的例子..如果您需要参考代码,请遵循上述答案。