【问题标题】:Calling an overridden method from the base class in Ada从 Ada 中的基类调用重写的方法
【发布时间】:2015-02-12 15:09:57
【问题描述】:

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

问题

我期望看到的是从ChildPrint1Print2 发送的调用。但是不会调用被覆盖的方法!有 C++ 背景,这种类型的多态调用对我来说很有意义,但我不知道 Ada 是如何对待它们的?

来自Prints 的调用Self.Print1; 错误吗?

【问题讨论】:

    标签: oop polymorphism ada


    【解决方案1】:

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

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

    (当然是在体内!)然后一切都按您的预期进行。

    [附注:我现在了解到 object.operation 表示法适用于类范围的对象!]

    【讨论】:

      【解决方案2】:

      考虑这一点的一种方法是从低层次考虑,即编译器生成什么代码。

      Prints 过程被编译并且看到Self.Print1 语句时,编译器生成的代码是一个非分派 调用。这意味着编译器计算出Print1 方法的地址(或外部符号),并生成对其的调用。这不是间接调用,而是对固定地址的调用,即出现在Parents 中的Print1。它不分派的原因是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)),那么调用将是 dispatching,这意味着它基本上是一个间接调用。代码会在运行时找出正确过程的地址,并对其进行间接调用。

      Ada 和 C++ 之间的区别在于,Ada 编译器使用它所操作的对象的类型 来确定是进行分派(间接)还是非分派(调用)。如果类型是类范围的,则它正在调度,否则它是固定的。然而,C++ 使用方法的属性而不是类型的属性来决定进行哪种调用;如果该方法被标记为virtual,那么对它的调用正在调度,如果没有,它们是固定的。 (至少我认为是这样的;我并不是真正的 C++ 专家。)

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

      Print1 (Self);
      

      这将是一个非调度呼叫;但是

      Print1 (Parent'Class (Self));
      

      是一个调度调用。

      【讨论】:

        猜你喜欢
        • 2012-12-12
        • 1970-01-01
        • 1970-01-01
        • 2011-01-18
        • 2023-03-12
        • 2011-06-17
        • 2010-12-05
        • 2017-05-05
        相关资源
        最近更新 更多