Как я могу заставить этот диспетчерский вызов работать? - PullRequest
0 голосов
/ 20 мая 2019

Я пытаюсь ознакомиться с объектной ориентацией в Аде.Ваш сайт помог мне с другой проблемой OO пару месяцев назад, и я надеюсь, что вы захотите помочь снова.

Ситуация: у меня есть абстрактный тип "токен" и 2 производных типа "otoken" и«vtoken».Я хочу поместить 2 производных типа в один и тот же массив и заставить их правильно распределяться.

Мой учебник рекомендовал объявлять массив как содержащий указатели на класс токенов, что заставляет меня работать с точками повсюду.Ниже приведена сокращенная версия моей программы, но она не будет компилироваться, потому что компилятор говорит, что мои диспетчерские вызовы «неоднозначны»

---------------------------------------------------------------------------------

--------------------------------------------
-- Tokensamp.ads
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
package tokensamp is
    type token is abstract tagged record
    x: integer;
    end record;
    type otoken is new token with record
    y: integer;
    end record;
    type vtoken is new token with record
    z: integer;
    end record;

    type potoken is access otoken;
    type pvtoken is access vtoken;

end tokensamp;
------------------------------------------------------------------------------------------------------
-- Parsesamp.ads:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with tokensamp; 
package parsesamp is
    function rootOfTree( t: tokensamp.pvtoken) return integer;
    function rootOfTree( t: tokensamp.potoken) return integer;  
end parsesamp; 
-------------------------------------------
-- parsesamp.adb:
package body parsesamp is 
    function rootOfTree( t: tokensamp.pvtoken) return integer  is
    begin
       return   t.z * 2;
    end rootOfTree;

    function rootOfTree( t: tokensamp.potoken) return integer is
    begin
        return  t.y * 2;
    end rootOfTree;
    result: integer;
    type tarray is array (1..2) of access tokensamp.token'class ;
    tl: tarray;
begin
    for i in 1..2 loop
    result := rootOfTree(  tl(i) );
    end loop;

end parsesamp;
-------------------------------------------------------------

Когда я компилирую это с моим компилятором GNAT Ada 95Я получаю сообщения об ошибках:

C:\GNAT\2018\bin\ceblang>gnatmake   parsesamp.adb
gcc -c parsesamp.adb
parsesamp.adb:25:27: ambiguous expression (cannot resolve "rootOfTree")
parsesamp.adb:25:27: possible interpretation at parsesamp.ads:9
parsesamp.adb:25:27: possible interpretation at parsesamp.ads:8
gnatmake: "parsesamp.adb" compilation error

Другими словами, он не может распознать две функции как альтернативные отправленные вызовы.Я был бы признателен, если бы вы посоветовали мне, поскольку я застрял в этом в течение нескольких дней.

Ответы [ 3 ]

1 голос
/ 20 мая 2019

Для начала вам нужно объявить rootOfTree как абстрактную операцию из token:

type token is abstract tagged record
   x: integer;
end record;
function rootOfTree( t: tokensamp.token) return Integer is abstract;

(примитивная операция должна быть объявлена ​​до того, как token будет заморожено , в основном перед тем, как использовать его как при объявлении производные типы).

Затем объявите примитивные операции otoken и vtoken; Oни должны быть объявлены в том же пакете, что и их соответствующий тип быть примитивным, то есть быть отправляемым.

type otoken is new token with record
   y: integer;
end record;

type vtoken is new token with record
   z: integer;
end record;

function rootOfTree( t: tokensamp.vtoken) return integer;
function rootOfTree( t: tokensamp.otoken) return integer;

(было бы более нормально объявить каждый сразу после его параметра типа, но поскольку ни один не замораживает другой, это нормально).

Обратите внимание, что ни одна из операций rootOfTree не принимает тип доступа параметр.

Вам не нужны potoken, pvtoken, хотя вы могли бы рассмотреть объявив указатель для всего класса здесь:

type ptoken is access token'class;

Затем вам нужно объявить тело для package tokensamp, содержащее реализации двух конкретных rootOfTree с.

Учитывая parsesamp, вы не должны объявлять либо rootOfTree здесь.

Вы можете написать

result := tokensamp.rootOfTree (t1(i).all);

(t1(i) - это указатель на класс, .all - на класс, и tokensamp.rootOfTree является диспетчерской операцией, так что это диспетчерский вызов)

.. или вы можете использовать более красивую стенографию

result := t1(i).rootOfTree;
1 голос
/ 20 мая 2019

Ваша путаница, похоже, включает в себя как использование пакетов, так и способ определения диспетчерских операций в Ada.Операции диспетчеризации должны быть определены в том же пакете, определяющем тип данных с тегами, но перед определением любых других типов.

package Tokens is
   type token is tagged private;
   function Root_Of_Tree(T : Token) return Integer;
   type Token_Access is access all Token'Class;
   type Token_Array is array (Positive range <>) of Token_Access;
private
   type Token is tagged record
      X : Integer := 1;
   end record;
end Tokens;

Спецификация пакета определяет маркер типа тегов и его операцию диспетчеризации Root_Of_Tree.Тип записи Token содержит один целочисленный элемент данных с именем X. Тело пакета:

    package body Tokens is

       ------------------
       -- Root_Of_Tree --
       ------------------

       function Root_Of_Tree (T : Token) return Integer is
       begin
          return T.X;
       end Root_Of_Tree;

    end Tokens;

Я использовал дочерние пакеты для определения типов Otoken и Vtoken.

package Tokens.OTokens is
   type Otoken is new Token with private;
   function Root_Of_Tree(T : Otoken) return Integer;
private
   type Otoken is new Token with record
      Y : Integer := 2;
   end record;

end Tokens.OTokens;

Тело Tokens.OTokens:

package body Tokens.OTokens is

   ------------------
   -- Root_Of_Tree --
   ------------------

   function Root_Of_Tree (T : Otoken) return Integer is
   begin
      return T.Y * 2;
   end Root_Of_Tree;

end Tokens.OTokens;

Спецификация Tokens.VTokens:

package tokens.vtokens is
   type vtoken is new token with private;
   function Root_Of_Tree(T : vtoken) return Integer;
private
   type vtoken is new token with record
      Z : Integer := 3;
   end record;

end tokens.vtokens;

Тело Tokens.Vtokens:

package body tokens.vtokens is

   ------------------
   -- Root_Of_Tree --
   ------------------

   function Root_Of_Tree (T : vtoken) return Integer is
   begin
      return T.Z * 2;
   end Root_Of_Tree;

end tokens.vtokens;

Основная процедура для создания массива, содержащего один otoken и один vtoken:

with Ada.Text_IO; use Ada.Text_Io;
with Tokens; use Tokens;
with Tokens.OTokens; use Tokens.OTokens;
with tokens.vtokens; use tokens.vtokens;

procedure Main is
   Ot : token_Access := new Otoken;
   Vt : token_access := new vtoken;
   Ta : Token_Array := (Ot, Vt);
begin
   for tk of Ta loop
      Put_Line(Integer'Image(Root_of_Tree(tk.all)));
   end loop;
end Main;

Хорошо помнить, что тип OToken содержит два поля, X и Y.Тип VToken содержит два поля X и Z. Вывод основной процедуры:

4
6
0 голосов
/ 20 мая 2019

В качестве дополнения к ответам, данным Джимом Роджерсом и Саймоном Райтом, если вы используете Ada 2012, вы можете рассмотреть возможность использования неопределенного держателя для создания массива (см. Также RM A.18.18 и Обоснование Ada 2012, раздел 8.5 )

Как указано в обосновании, держатель - это контейнер, который может содержать (и управлять) один экземпляр объекта. Объект, передаваемый в качестве аргумента подпрограмме To_Holder (см. Пример ниже), копируется в экземпляр кучи, который, в свою очередь, уничтожается, когда он больше не нужен (например, когда он заменяется или когда держатель выходит из -объем). Следовательно, контейнер-держатель освобождает вас от ручного управления памятью, как если бы вы использовали типы access напрямую.

Стоимость (производительность) заключается в том, что объект, переданный в программу To_Holder, копируется. Вы можете «перемещать» объекты между держателями (используя подпрограмму Move, определенную в пакете держателей), но вы не можете «перемещать» объект в держатель; Вы можете только скопировать его в держатель.

token.ads (spec)

package Tokens is

   --  Abstract Root Type.

   type Token is abstract tagged private;   
   function Root_Of_Tree (T : Token) return Integer is abstract;

   --  First derived type.

   type OToken is new Token with private;         
   function Root_Of_Tree (T : OToken) return Integer;   
   function Create_OToken (X, Y : Integer) return OToken;

   --  Second derived type.

   type VToken is new Token with private;   
   function Root_Of_Tree (T : VToken) return Integer;   
   function Create_VToken (X, Z : Integer) return VToken;

private

   type Token is abstract tagged record
      X : Integer;
   end record;

   type OToken is new Token with record
      Y : Integer;
   end record;

   type VToken is new Token with record
      Z : Integer;
   end record;

end Tokens;

tokens.adb (тело)

package body Tokens is   

   function Root_Of_Tree (T : OToken) return Integer is
   begin
      return T.X + 2 * T.Y;
   end Root_Of_Tree;   

   function Create_OToken (X, Y : Integer) return OToken is
   begin
      return OToken'(X, Y);
   end Create_OToken;   

   function Root_Of_Tree (T : VToken) return Integer is
   begin
      return T.X + 3 * T.Z;
   end Root_Of_Tree;

   function Create_VToken (X, Z : Integer) return VToken is
   begin
      return VToken'(X, Z);
   end Create_VToken;

end Tokens;

main.adb

with Ada.Text_IO; use Ada.Text_IO;
with Tokens;      use Tokens;

with Ada.Containers.Indefinite_Holders;

procedure Main is

   package Token_Holder is
     new Ada.Containers.Indefinite_Holders (Token'Class);
   use Token_Holder;

   type Token_Array is array (Integer range <>) of Holder;


   Tokens : Token_Array :=
     (To_Holder (Create_OToken (1, 2)),
      To_Holder (Create_OToken (5, 4)),
      To_Holder (Create_VToken (1, 2)),
      To_Holder (Create_VToken (5, 4)));

begin

   for T of Tokens loop
      Put_Line (Integer'Image (T.Element.Root_Of_Tree));
   end loop;

end Main;

Запуск valgrind показывает, что после завершения программы память не выделяется:

$ valgrind ./main
==1392== Memcheck, a memory error detector
==1392== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1392== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==1392== Command: ./main
==1392== 
 5
 13
 7
 17
==1392== 
==1392== HEAP SUMMARY:
==1392==     in use at exit: 0 bytes in 0 blocks
==1392==   total heap usage: 8 allocs, 8 frees, 160 bytes allocated
==1392== 
==1392== All heap blocks were freed -- no leaks are possible
==1392== 
==1392== For counts of detected and suppressed errors, rerun with: -v
==1392== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Примечание [обновлено]: существует 8 распределений, в то время как массив содержит только 4 элемента / держателя. Это связано с тем, как держатель реализован для платформ, которые поддерживают атомные приращения / убывания (например, Linux). Для этих платформ реализация создает внутри себя другого «общего держателя», который поддерживает стратегию копирования при записи (см. source ). Для платформ, которые не поддерживают атомарные приращения / убывания, реализация будет более простой (см. source ) и будут показаны только 4 выделения.

...