【问题标题】:Dynamic dispatching in AdaAda 中的动态调度
【发布时间】:2016-03-12 16:43:21
【问题描述】:

即使使用这个简单的示例,我也无法让动态调度正常工作。我相信问题在于我如何设置类型和方法,但看不到在哪里!

with Ada.Text_Io;
procedure Simple is

   type Animal_T is abstract tagged null record;

   type Cow_T is new Animal_T with record
      Dairy : Boolean;
   end record;

   procedure Go_To_Vet (A : in out Cow_T) is
   begin
      Ada.Text_Io.Put_Line ("Cow");
   end Go_To_Vet;

   type Cat_T is new Animal_T with record
      Fur : Boolean;
   end record;

   procedure Go_To_Vet (A : in out Cat_T)
   is
   begin
      Ada.Text_Io.Put_Line ("Cat");
   end Go_To_Vet;

   A_Cat : Cat_T := (Animal_T with Fur => True);
   A_Cow : Cow_T := (Animal_T with Dairy => False);
   Aa : Animal_T'Class := A_Cat;
begin

   Go_To_Vet (Aa); -- ERROR This doesn't dynamically dispatch!
end Simple;

【问题讨论】:

    标签: ada


    【解决方案1】:

    两件事:

    首先,您必须有一个 Go_To_Vet 的抽象规范,这样才能进行委托(这也引起了我的注意:-):

    procedure Go_To_Vet (A : in out Animal_T) is abstract;
    

    第二个是Ada要求父定义在自己的包中:

    package Animal is
    
       type Animal_T is abstract tagged null record;
    
       procedure Go_To_Vet (A : in out Animal_T) is abstract;
    
    end Animal;
    

    您的 Simple 过程中的类型定义需要相应地调整(这里我只是使用 Animal 包来保持简单):

    with Ada.Text_Io;
    with Animal; use Animal;
    procedure Simple is
    
       type Cow_T is new Animal_T with record
          Dairy : Boolean;
       end record;
    
       procedure Go_To_Vet (A : in out Cow_T) is
       begin
          Ada.Text_Io.Put_Line ("Cow");
       end Go_To_Vet;
    
       type Cat_T is new Animal_T with record
          Fur : Boolean;
       end record;
    
       procedure Go_To_Vet (A : in out Cat_T)
       is
       begin
          Ada.Text_Io.Put_Line ("Cat");
       end Go_To_Vet;
    
       A_Cat : Cat_T := (Animal_T with Fur => True);
       A_Cow : Cow_T := (Animal_T with Dairy => False);
       Aa : Animal_T'Class := A_Cat;
    begin
    
       Go_To_Vet (Aa); -- ERROR This doesn't dynamically dispatch! DOES NOW!!  :-)
    end Simple;
    

    编译:

    [17] Marc say: gnatmake -gnat05 simple
    gcc -c -gnat05 simple.adb
    gcc -c -gnat05 animal.ads
    gnatbind -x simple.ali
    gnatlink simple.ali
    

    最后:

    [18] Marc say: ./simple
    Cat
    

    【讨论】:

    • 谢谢 Marc,我知道是这样的!我较小的问题是如何将 a_Cow 分配给 aa ? (aa := a_cow;抱怨!)
    • +1 好例子。 @NWS:禁止分配,如相邻的answer 中所述。
    • 有趣的是,如果没有两个具体的Go_To_Vet 程序的子程序规范,您就可以逃脱。不过,您确实必须将主体放在类型声明之后。
    • @Simon Wright:它只适用于第一个子程序;第二个产生“覆盖 name 为时已晚。”令人高兴的是,错误消息是“规范应该紧跟在类型之后。”
    • @Simon Wright:不经意间忽略了抽象子程序规范让我不止一次拔掉了头发(只剩下很少的部分)。不过幸运的是,由于这些经验,现在这是我在设置调度时遇到问题时首先检查的事情之一。
    【解决方案2】:

    如何将 A_Cow 分配给 Aa ? (Aa := A_Cow;抱怨!)

    你不能也不应该。尽管它们共享一个共同的基类,但它们是两种不同的类型。与 Java 相比,将猫转换为牛的尝试会在运行时导致 ClassCastException。 Ada 在编译时排除了问题,就像 Java 泛型声明一样。

    我扩展了@Marc C 的示例来展示您如何可以调用基类子程序。请注意prefixed notationprocedure Simple 中的使用。

    附录:正如您提到的class wide programming,我应该添加与以下示例相关的几点。特别是,类范围的操作,例如Get_WeightSet_Weight,是not inherited,但prefixed notation 使它们可用。此外,这些子程序相当做作,因为标记的记录组件可以直接访问,例如Tabby.Weight.

    package Animal is
    
       type Animal_T is abstract tagged record
          Weight : Integer := 0;
       end record;
    
       procedure Go_To_Vet (A : in out Animal_T) is abstract;
       function  Get_Weight (A : in Animal_T'Class) return Natural;
       procedure Set_Weight (A : in out Animal_T'Class; W : in Natural);
    
    end Animal;
    
    package body Animal is
    
       function Get_Weight (A : in Animal_T'Class) return Natural is
       begin
          return A.Weight;
       end Get_Weight;
    
       procedure Set_Weight (A : in out Animal_T'Class; W : in Natural) is
       begin
          A.Weight := W;
       end Set_Weight;
    
    end Animal;
    
    with Ada.Text_IO; use Ada.Text_IO;
    with Animal; use Animal;
    procedure Simple is
    
       type Cat_T is new Animal_T with record
          Fur : Boolean;
       end record;
    
       procedure Go_To_Vet (A : in out Cat_T)
       is
       begin
          Ada.Text_Io.Put_Line ("Cat");
       end Go_To_Vet;
    
       type Cow_T is new Animal_T with record
          Dairy : Boolean;
       end record;
    
       procedure Go_To_Vet (A : in out Cow_T) is
       begin
          Ada.Text_Io.Put_Line ("Cow");
       end Go_To_Vet;
    
       A_Cat : Cat_T := (Weight => 5, Fur => True);
       A_Cow : Cow_T := (Weight => 200, Dairy => False);
       Tabby : Animal_T'Class := A_Cat;
       Bossy : Animal_T'Class := A_Cow;
    
    begin
       Go_To_Vet (Tabby);
       Put_Line (Tabby.Get_Weight'Img);
       Go_To_Vet (Bossy);
       Put_Line (Bossy.Get_Weight'Img);
       -- feed Bossy
       Bossy.Set_Weight (210);
       Put_Line (Bossy.Get_Weight'Img);
    end Simple;
    

    【讨论】:

    • @NWS:我很欣赏复选标记,但@Marc C 的回答先于我。如果你改变主意,我会很乐意服从他。而且,当然,您始终可以对您认为有用的任何答案进行投票。 :-)
    • 为你的宽宏大量投赞成票(这是一个很好的阐述:-)
    • 可以通过使变量 aa 成为对类范围类型的访问来进行赋值;然而,有一些限制要记住:1)你不能直接分配[访问]一个不同类的项目,你必须使用NEW; 2) 如果由于某种原因,您尝试动态构造一个类范围的变量 [non-access](例如从文件或键盘),那么您可能应该实例化 Generic_Dispatching_Constructor,请参阅:adaic.org/resources/add_content/standards/05rat/html/…
    • @Shark8:感谢link。我不禁注意到与newInstance() 的相似之处。
    猜你喜欢
    • 1970-01-01
    • 2023-02-10
    • 1970-01-01
    • 2012-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-27
    相关资源
    最近更新 更多