在Ada中从基类调用重写的方法

4
我想知道如何在ADA中从父类调用被覆盖的方法。考虑以下示例。一个名为Parent的类有一些方法,这些方法被Child类覆盖。在Parent类中有一个方法(即Prints)调用了一些它被覆盖的方法。但是被覆盖的方法并没有被调用!以下是示例:

--- 父类 ---

package Parents is 
    type Parent is tagged null record;

    procedure Prints(Self: in out Parent); 

    -- these will be overridden  
    procedure Print1(Self: in out Parent) is null;
    procedure Print2(Self: in out Parent) is null;        
end Parents;
...
package body Parents is        
    procedure Prints(Self: in out Parent) is
    begin
        Put_Line("Parents.Prints: calling prints...");
        Self.Print1;
        Self.Print2;
    end;        
end Parents;

--- 子元素 ---
With Parents;
package Childs is 
    type Child is new Parents.Parent with null record; 

    overriding procedure Print1(Self: in out Child);
    overriding procedure Print2(Self: in out Child);    
end Childs;
...
package body Childs is         
    procedure Print1(Self: in out Child) is
    begin
        Put_Line("Child.Print1 is printing...");
    end;

    procedure Print2(Self: in out Child) is
    begin
        Put_Line("Child.Print2 is printing...");
    end;        
end Childs;

---主函数---

procedure Main is  
    anyprint : access Parents.Parent'Class;
begin           
    anyprint := new Childs.Child;
    anyprint.Prints;
end Main;

问题

我希望能看到从Child分派给Print1Print2的调用。但是覆盖的方法没有被调用!考虑到我的C ++背景,这种多态调用对我来说很有意义,但是我无法弄清楚Ada如何处理它们?

Prints中的Self.Print1;调用是否有误?

2个回答

5
在Ada中,调度仅在对象属于类广泛类型时发生。相关的手册部分是ARM 3.9.2
Parents.Prints中,控制操作数Self的类型为Parent,并且是“静态标记的”,因此没有调度。
一种方法是使用“重新调度”,它看起来像这样:
procedure Prints(Self: in out Parent) is
begin
   Put_Line("Parents.Prints: calling prints...");
   Parent'Class (Self).Print1;
   Parent'Class (Self).Print2;
end;

在这里,“Parent'Class (Self)”的视图转换意味着调用“.Print1”方法时,对象被“动态标记”,并进行调度。

像现在这样,可以在派生类型中重写“Prints”。但这通常不是你想要的。如果不是这样,将其参数更改为“class-wide”可能更合适:

procedure Prints(Self: in out Parent'Class);

(当然在主体中!)然后一切就像您所期望的那样工作。

【一个副注:现在我已经了解到,object.operation符号可以用于类范围的对象!】


3

一种思考这个问题的方法是从低层次考虑,即编译器生成的代码。

当编译Prints过程时,看到语句Self.Print1,编译器生成的代码是一个非分派调用。这意味着编译器找出Print1方法的地址(或外部符号),并生成对它的调用。这不是一个间接调用,而是对一个固定地址的调用,该地址将是Parents中出现的Print1。之所以它是非分派的,是因为Self的类型不是类广泛类型(它只是Parent,而不是Parent'Class)。

当你声明Child类型时,它将继承Prints过程。也就是说,有一个隐式声明的过程看起来像这样:

procedure Prints (Self : in out Child);  -- inherited procedure

但是,如果您不覆盖它,编译器不会为此隐式过程生成新代码。因此,当调用“Prints”时,即使使用“Child”参数调用,代码也将与使用“Parent”参数调用相同。正如上一段所解释的,代码将进行固定调用而不是分派(间接)调用,并且仍将声明在“Parent”中的“Print1”和“Print2”作为代码生成,因为在编译“Parent”时已生成了代码。
回到“Prints”的调用:
Self.Print1;

如果Self的类型是Parent'Class,或者你使用了视图转换将其转换为Parent'Class,就像Simon的回答中使用了Parent'Class(Self),那么这个调用将是分派的,这意味着它基本上是一次间接调用。代码将在运行时确定正确过程的地址,并进行间接调用。
Ada和C ++之间的区别在于Ada编译器使用它操作的对象的类型来确定是否进行分派(间接)或非分派(调用)。如果类型是classwide,则是分派的,否则是固定的。然而,C ++使用方法的属性而不是类型的属性来决定要进行哪种类型的调用;如果该方法标记为virtual,则调用将进行分派,否则它们将保持固定。(至少我想这是这样的;我不是一个真正的C++专家。)
顺便说一下,即使您不使用Object.Operation符号,情况也是一样的。如果您不是Self.Print1,而是
Print1 (Self);

那将是一个非分派调用;但是。
Print1 (Parent'Class (Self));

是一个调度调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接