没有对象或虚拟函数的运行时多态性

2020-05-15 23:01:02

当考虑多态性,特别是运行时多态性时,首先想到的是虚函数。

虚拟函数非常强大,适合某些用例。但在使用它们之前,最好考虑一下我们对多态性的确切需求,看看是否有其他更适合的工具来满足它。

实际上,虚函数会在对象上创建多态性。但是,如果您不需要对象怎么办?如果您只需要您的代码根据某些条件以不同的方式运行,但是您不需要任何涉及的对象,该怎么办呢?

请考虑下面的示例,它的灵感来自于我参与的一个项目。我简化了示例,去掉了与域相关的所有内容,使其更易于理解。

我们有一个输入,我们想要计算一个输出(这是一个相当标准化的例子,对吧?)。输入值如下所示:

有各种类型的计算器,旨在处理各种类型的输入。为了使示例简单,但又不失其一般性,我们假设有两个计算器:一个处理大输入(值大于10),另一个处理小输入(值小于或等于10)。

此外,每个计算器可以记录有关给定输入和输出对的一些信息。

考虑到上述需要,我们需要一些接口来表示具有以下三个功能的计算器:

布尔句柄(输入常量输入);输出计算(输入常量输入);无效日志(输入常量输入,输出常量输出);

将这三个函数分组在同一位置(例如,一个类)会很好。但是我们不需要它们是成员函数,它们可以只是常规函数。如果我们使用一个类将它们粘合在一起,我们可以将它们实现为静态函数。

struct BigCalculator{static bool Handles(input const&;input){return input.value>;10;}静态输出计算(input const&;input){return output{input.value*5};}静态无效日志(input const;amp;input,output const&;output){std::cout<;<;";BigCalculator接受";<;<;in.。<;<;output.value<;<;';\n';;}};

std::cout<;<;";BigCalculator接受";<;<;INPUT的输入。值,并产生了#34;<;<;输出。Value<;<;';\n';;

struct SmallCalculator{static bool Handles(input const&;input){return input.value<;=10;}静态输出计算(input const&;input){return output{input.value+2};}静态无效日志(input const;input,output const&;output){std::cout<;<;";SmallCalculator接受";<;<;的输入。并产生了";<;<;output.value;<;';\n&39;;}};

std::cout<;<;";SmallCalculator接受";<;<;INPUT的输入。值,并产生了#34;<;<;输出。Value<;<;';\n';;

现在我们已经有了Calculator接口的各种实现,我们需要以某种方式将它们以统一的方式绑定到调用点。

这意味着给定调用点的代码应该独立于它使用的特定计算器。根据定义,这就是多态性实现的目的。

到目前为止,“Calculator”接口是隐式的。现在让我们创建一个包含Calculator的组件,它的行为既可以像SmallCalculator,也可以像BigCalculator。

此组件必须具有Calculator接口的三个功能,并执行BigCalculator或SmallCalculator的代码。让我们添加三个函数指针,稍后我们将为其分配计算器实现的静态函数:

结构计算器{bool(*handles)(输入常量(&;input);输出(*计算)(输入常量(&;input));void(*log)(输入常量(&;input,输出常量(&;output));};

为了使与计算器实现的绑定更容易,让我们添加一个帮助器函数,该函数将这些函数指针分配给计算器的函数指针:

结构计算器{bool(*Handles)(输入常量输入);输出(*计算)(输入常量输入);void(*log)(输入常量输入,输出常量输出);模板<;TypeName CalculatorImplementation>;静态计算器createFrom(){return Calculator{&;CalculatorImplementation:Handles,&;CalculatorImplementation:

此函数有点像构造函数,但它不像普通构造函数那样接受值,而是接受类型作为输入。

要解决在几个计算器中选择正确计算器的初始问题,让我们实例化这些计算器并将其存储在一个集合中。为此,我们将拥有一个绑定到BigCalculator或SmallCalculator的计算器集合:

我们现在可以编写使用Calculator接口的代码,并且独立于各种类型的计算器:

自动常量输入=输入{50};自动常量计算器=getCalculators();自动常量计算器=std::find_if(BEGIN(计算器),END(计算器),[&;Input](自动&;&;计算器){Return Calculator.Handles(INPUT);});IF(COMPATOR!=END(计算器)){AUTO CONST OUTPUT=COMPUTOR-&>COMPUTE(INPUT);COMPATOR-&>;LOG(INPUT,

如果我们将第一行替换为以下内容,以获取一小部分输入:

我们看到代码选择了正确的计算器,并使用它来执行计算和日志记录。

上面的代码既不包含继承,也不包含关键字virtual。但是它使用函数指针将执行路由到给定类中的实现,这听起来很像虚函数和vtable所做的事情。

我们是不是刚刚手动实现了虚拟功能?在这种情况下,我们最好使用语言的本机特性,而不是实现我们自己的特性。

我们试图解决的问题确实可以用虚函数来实现。以下是执行此操作的代码,并突出显示了与前面代码的显著差异:

struct input{Double Value;};struct Output{Double Value;};struct Calculator{虚拟布尔句柄(输入常量&;input)常量=0;//虚方法虚拟输出计算(输入常量&;input)常量=0;虚拟无效日志(输入常量输入,输出常量&;输出)常量=0;Virtual~Calculator(){};};struct BigCalculator:计算器//继承{bool Handles(输入常量。}output computer(input const&;input)const覆盖{return output{input.value*5};}void log(input const&;input,output const&;output)const覆盖{std::cout<;<;";BigCalculator接受";<;<;input.value<;";的输出,并生成";<;&输出。\n';;}};struct SmallCalculator:计算器{bool Handles(input const&;input)const覆盖{return input.value<;=10;}output computer(input const&;input)const覆盖{return output{input.value+2};}void log(input const;input,output const&;output)const覆盖{std::cout<;<;";SmallCalculator Take";并产生了输出";<;<;output.value;<;';\n&39;;}};std::vector<;std::unique_ptr<;Calculator>;>;getCalculators()//Unique_ptrs{自动计算器=std::vector<;std::unique_ptr<;Calculator>;>;{};calculators.push_back(std::make_unique<;BigCalculator>;());calculators.push_back(std::make_unique<;SmallCalculator>;());返回计算器;}int Main(){自动常数输入=输入{50};自动常数计算器=getCalculators();自动常数计算器=std::Find_if(开始(计算器),结束(计算器),[&;输入](自动&;&;计算器){返回计算器-&>句柄(输入);});IF(计算器!=END(计算器)){自动常量输出=(*计算器)->;COMPUTE(输入);//额外间接(*计算器)->;log(输入,输出);}}。

std::cout<;<;";BigCalculator接受";<;<;INPUT的输入。值,并产生了#34;<;<;输出。Value<;<;';\n';;

std::cout<;<;";SmallCalculator接受";<;<;INPUT的输入。值,并产生了#34;<;<;输出。Value<;<;';\n';;

与我们前面没有使用虚函数的代码相比,有一些显著的不同之处:

这两种方法之间的结构差异在于,第一种方法是在类或代码上使用多态性,而具有虚函数的方法是在对象上使用多态性。

因此,多态对象在堆上实例化,以便将它们存储在容器中。使用类上的多态性,我们没有实例化堆上的任何对象。

使用新选项(和删除)可能会有问题,特别是在性能方面。出于这个原因,一些应用程序甚至被禁止使用堆存储。

但是,如果您的系统允许使用new,最好编写富有表现力的代码,并仅在必要时对其进行优化。也许在代码的这一部分中,调用new并不会有什么显著的不同。

我们的第一个在类上使用多态性的代码在表现力方面有一个缺点:它使用非标准结构,使用Calculator接口处理函数指针。另一方面,虚拟函数只使用隐藏所有这些绑定的标准功能,并且提供更少的代码可读。

另一方面,虚函数不像类上的多态性那样精确地表达我们的意图:计算器不是对象,它们是函数。将多态性与类一起使用的解决方案通过使用静态函数而不是对象方法来演示这一点。

总而言之,当谈到表现力时,两种解决方案都有利弊。当谈到new的使用时,一个解决方案使用new,一个不使用。

在任何情况下,重要的是要记住,虚函数是强大的,因为它们允许对象级别的多态性,但它们是有代价的:堆上的实例化和使用指针。

当您需要多态性时,不要急于使用虚函数。就此而言,不要仓促做任何设计。首先想一想你需要什么。可能还有其他解决方案可以更好地满足您的需求。

成为守护神!分享这篇帖子!你不想错过吗?(&;nbsp)。以下是: