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

时间:2015-02-12 15:09:58

标签: oop polymorphism ada

我想知道如何从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;

问题

我希望看到的是从Print1发送给Print2Child的来电。但是被覆盖的方法没有被调用!拥有C ++背景,这种类型的多态调用对我来说很有意义,但我无法弄清楚Ada如何对待它们?

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

2 个答案:

答案 0 :(得分:5)

在Ada中,仅当对象属于类范围类型时才会进行调度。相关手册部分为ARM 3.9.2

Parents.Prints中,控制操作数Self的类型为Parent,并且是“静态标记”,因此没有调度。

一种方法是使用“redispatching”,如下所示:

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能够在派生类型中被覆盖。这并不总是(甚至通常?)你想要的。如果不是,则将其参数更改为类范围是合适的:

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

(当然是在体内!)然后一切都按照你的预期运作。

[旁注:我现在已经知道 object.operation 表示法适用于全班对象!]

答案 1 :(得分:3)

考虑这一点的一种方法是在较低级别考虑它,即编译器生成什么代码。

编译Prints过程并看到语句Self.Print1时,编译器生成的代码是非调度调用。这意味着编译器会为Print1方法计算出地址(或外部符号),并生成对它的调用。它不是间接呼叫,而是对固定地址的呼叫,它将是Print1中出现的Parents。它不发送的原因是Self的类型不是全班类型(它只是Parent,而不是Parent'Class)。

当您声明Child类型时,它将继承Prints过程。也就是说,有一个隐式声明的过程如下所示:

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

但是,如果您不覆盖它,编译器不会为此隐式过程生成新代码。因此,当调用Prints时,即使使用Child参数调用它,代码也会与Parent参数相同。如前一段所述,代码对Print1Print2进行固定调用而不是调度(间接)调用,这仍然是Parent中声明的调用。 ,因为代码是在编译Parent时生成的。

返回Prints中的电话:

Self.Print1;

如果Self的类型为Parent'Class,或者您使用了视图转换将其转换为Parent'Class,就像在Simon的回答(Parent'Class(Self))中那样,那么呼叫将是调度,这意味着它基本上是间接呼叫。代码将在运行时计算出正确过程的地址,并对其进行间接调用。

Ada和C ++之间的区别在于Ada编译器使用其操作对象的类型来确定是进行调度(间接)还是非调度(调用) )。如果类型是全级的,它是调度的,否则它是固定的。但是,C ++使用方法的属性而不是类型的属性来决定要进行哪种调用;如果方法标记为virtual,则调用它是调度,如果不是,则修复它们。 (至少我认为是这样的;我并不是真正的C ++专家。)

顺便说一句,即使您不使用Object.Operation表示法也是如此。如果,而不是Self.Print1,你说

Print1 (Self);

这将是一个非调度电话;但

Print1 (Parent'Class (Self));

是一个调度电话。