Как реализовать Unchecked_Access

Я пытаюсь снова спроектировать двумерный массив, который расширяется автоматически.

Прямоугольные.объявления

generic
    type Value_Type is private;

package Rectangular is
    
    function Get ( Row, Col : Integer) return Value_Type;
    procedure Set ( Row, Col : Integer; Value : Value_Type);
    
private
    type Matrix is array (Integer range <>, Integer range <>) of aliased Value_Type;
    
    Item : access Matrix;
    
end Rectangular;

Прямоугольный.adb

package body Rectangular is

    function Create (Rowmin, Rowmax, Colmin, Colmax : Integer) return access Matrix is
    begin
        return Answer :  constant access Matrix :=
          new Matrix (Rowmin .. Rowmax, Colmin .. Colmax)
        do
            null; -- maybe something later...
        end return;
    end Create;
    
    procedure Adjust_Bounds (Row, Col : Integer) is
        
        Rowmin, Rowmax, Colmin, Colmax : Integer;
        Newitem :  access Matrix;
        
    begin
        
        if Row >= Item'First (1) and Row <= Item'Last (1) and
          Col >= Item'First (2) and Col <= Item'Last (2) then
            return;
        end if;

        -- Matrix needs expanding, establish new bounds
        Rowmin := Integer'Min (Item'First (1), Row);
        Rowmax := Integer'Min (Item'Last (1), Row);
        Colmin := Integer'Min (Item'First (2), Col);
        Colmax := Integer'Min (Item'Last (2), Col);
    
        Newitem := Create (Rowmin, Rowmax, Colmin, Colmax);
        
        -- Copy old to new
        for R in Item'Range (1) loop
            for C in Item'Range (2) loop
                Newitem (R, C) := Item (R, C);
            end loop;
        end loop;
        
        -- How to free Item here?
        Item := Newitem;
        
    end Adjust_Bounds;
    
    function Get (Row, Col : Integer) return Value_Type is
        Result : Value_Type;
    begin
        Adjust_Bounds (Row, Col);
        Result := Item (Row, Col);
        return Result;
    end Get;
    
    procedure Set ( Row, Col : Integer; Value : Value_Type) is
    begin
        Adjust_Bounds (Row, Col);
        Item (Row, Col) := Value;
    end Set;
    
begin
    Item := Create (0, 0, 0, 0);

end Rectangular;

main.adb

with Ada.Text_IO; use Ada.Text_IO;
with Rectangular;
procedure Main is
begin
    declare
        package Rect is new Rectangular (Value_Type => Integer);
        X : Integer;
    begin
        -- Only 0,0 exists initially
        Rect.Set (0, 0, 2);
        X := Rect.Get (0, 0);
        Put_Line (X'Image);
        
        -- Make the matrix expand
        Rect.Set (1, 1, 42);
        X := Rect.Get (1, 1);
        Put_Line (X'Image);
    end;
end Main;

Это компилируется, но с

   6:17 warning: "Program_Error" will be raised at run time
   6:17 warning: accessibility check failure
   6:17 warning: in instantiation at rectangular.adb:29

и я, конечно, получаю сообщение PROGRAM_ERROR : angular.adb:59 проверка доступности не удалась, когда я пытаюсь запустить ее.

Я не понимаю, почему, поскольку «Rect» явно недоступен за пределами блока;

  1. Должен ли я использовать Unchecked_Access, чтобы избежать такого поведения? Если да, то как бы это выглядело?
  2. Если нет, то какая идиома правильная?
  3. Как мне освободить «Элемент» в Rectangular.adb?

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


person smirkingman    schedule 09.02.2021    source источник
comment
Просто используйте Ada.Containers.   -  person user_1818839    schedule 09.02.2021
comment
не используйте анонимные типы доступа.   -  person egilhh    schedule 09.02.2021
comment
Насколько я понимаю, @BrianDrummond Ada.Containers не имеет двумерных массивов. Мой вариант использования - это матрица (строки, столбцы: целое число) 1-байтовых состояний, представляющих область на земле; карта должна расширяться по мере того, как робот открывает новую местность. Вектор векторов кажется неуклюжим, и Map будет использовать много памяти с указателями на 1-байтовый флаг. Исходя из .Net, я, возможно, совершенно неправильно понял философию Ады, любые указатели будут приветствоваться.   -  person smirkingman    schedule 09.02.2021
comment
@egilhh Какая часть моего кода имеет анонимный тип доступа? Является ли «матрица доступа» анонимной?   -  person smirkingman    schedule 09.02.2021
comment
@smirkingman, да, все места, где у вас есть access Matrix, анонимны и, следовательно, отличаются от других по типу, с разными уровнями доступности.   -  person egilhh    schedule 09.02.2021
comment
@smirkingman, вы уверены, что карта не лучше подходит для этой проблемы? Двумерный массив экспоненциально расширяется в использовании памяти каждый раз, когда ваш робот выходит за текущие границы, тратя впустую целые строки сканирования, достойные памяти... Не говоря уже обо всем происходящем копировании, которое будет продолжать замедлять вашу программу, пока ваш робот исследует   -  person egilhh    schedule 09.02.2021
comment
@egilhh Хороший вопрос, и да, и нет. Матрица расширяется только тогда, когда робот (например, газонокосилка) исследует ее. Как только он обнаружил весь сад, матрица более или менее фиксирована, поэтому не имеет значения, дорого ли исследование. Используя один байт для состояния каждого 10-сантиметрового квадрата, это всего 300 КБ для сада площадью 3000 квадратных метров, которые легко поместятся в кэше небольшого SBC.   -  person smirkingman    schedule 09.02.2021
comment
@smirkingman, если вы знаете размер заранее, вы можете просто предварительно выделить фиксированный массив, типы доступа не требуются   -  person egilhh    schedule 09.02.2021
comment
Ada.Containers предоставляет тип Vector, который примерно соответствует расширяемому одномерному массиву. Можно имитировать расширяемый двумерный массив, создав вектор, содержащий элементы другого вектора. Это примерно эквивалентно созданию массива массивов.   -  person Jim Rogers    schedule 09.02.2021
comment
Зачем тебе личная часть? У вас нет частных типов и дочерних пакетов.   -  person Jeffrey R. Carter    schedule 10.02.2021
comment
@JeffreyR.Carter Вы правы, в приватной части нет необходимости, все может быть общедоступным. Тем не менее, чем меньше вы выставляете напоказ публично, тем меньше потом можно будет сломать.   -  person smirkingman    schedule 11.02.2021
comment
Они не должны быть общедоступными; они могут быть в теле.   -  person Jeffrey R. Carter    schedule 12.02.2021


Ответы (3)


Добавить после type Matrix

type Matrix_P is access Matrix;

(используйте свое собственное соглашение для именования типов доступа).

Затем глобально замените access Matrix на Matrix_P.

Затем в Adjust_Bounds вам, похоже, нужно заменить

      Rowmax := Integer'Min (Item'Last (1), Row);

by

      Rowmax := Integer'Max (Item'Last (1), Row);

и аналогично для Colmax.

person Simon Wright    schedule 09.02.2021
comment
Большое спасибо, я не понял, что «матрица доступа» анонимна. Теперь работает хорошо. Могу я спросить вас, как мне «освободить» старую матрицу, пожалуйста? Спасибо! - person smirkingman; 09.02.2021

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

with Ada.Containers.Vectors;
generic
   type Index_Type is range <>;
   with package inner_vector is new Ada.Containers.Vectors(<>);
package Vector_Of_Vectors is
   package V_Matrix is new Ada.Containers.Vectors(Index_Type   => Index_Type,
                                                  Element_Type => Inner_Vector.Vector,
                                                  "="          => Inner_Vector."=");
   use Inner_Vector;
end Vector_Of_Vectors;

Этот шаблон будет концептуально имитировать массив массивов

type foo is array (Positive range 1..10) of Integer;
type bar is array (Natural range 0..9) of foo;

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

Ниже приведен небольшой пример создания пакета Vector_Of_Vectors:

with Ada.Containers.Vectors;
with Vector_Of_Vectors;
with Ada.Text_IO; use Ada.Text_IO;
use Ada.Containers;

procedure Main is
   package Int_Vector is new Ada.Containers.Vectors(Index_Type => Natural,
                                                    Element_Type => Integer);
   use Int_Vector;
   
   package Int_Matrix is new Vector_Of_Vectors(Index_Type   => Natural,
                                               inner_vector => Int_Vector);
   use Int_Matrix;
   
   Temp_Vect : Int_Vector.Vector;
   Temp_Mat  : V_Matrix.Vector;
begin

   Temp_Vect := Int_Vector.Empty_Vector;
   for I in 1..5 loop
      Temp_Vect.append(I);
   end loop;
   
   Temp_Mat.Append(Temp_Vect);
    
   temp_Vect := Int_Vector.Empty_Vector;
    
   for I in 15..25 loop
      Temp_Vect.append(I);
   end loop;
   Temp_Mat.Append(Temp_Vect);
     
   for V of Temp_Mat loop
      for I of V loop
         Put(I'Image);
      end loop;
      New_Line;
   end loop;
end Main;
person Jim Rogers    schedule 09.02.2021
comment
Спасибо, а как мне его создать? пакет Rect является новым Vector_Of_Vectors (Index_Type =› Integer, Inner_Vector =› ??? - person smirkingman; 10.02.2021
comment
Пример для целочисленных значений: пакет Int_Vect — это новый Ada.Containers.Vector(Index_type => Natural, Element_Type => Integer); пакет int_matrix — новый Vector_Of_Vectors (Integer, Int_Vect); - person Jim Rogers; 11.02.2021
comment
Спасибо за продолжение. К сожалению, векторы не могут работать в моем случае использования: скажем, у меня есть Vector_of_Vectors (занято: логическое значение), который пуст. Я добавляю (0,0) и устанавливаю его в Occupied:=False, потому что я стою там. Я хочу знать, занята ли ячейка (5,5) (я намерен двигаться по (1,1)..(5,5)). Я не могу сказать, если ячейка (5,5) = занята, потому что ни один из векторов не имеет ничего из (1..5,1..5), поэтому я обязан добавить пустые записи (1..5,1 ..5), прежде чем я смогу обратиться к ячейке (5,5). Или я что-то совсем не так понял? - person smirkingman; 11.02.2021
comment
Если вы используете массив, вы создадите массив и инициализируете его элементы значением, например False или True. Затем вы измените каждый элемент массива в соответствии с правилами вашего приложения. Вы можете сделать то же самое с векторами. Конечно, синтаксис для доступа к векторным элементам более подробный, чем для массивов. - person Jim Rogers; 12.02.2021

Вот возможное решение для динамически самонастраивающегося двумерного массива. Применение:

package Rect is new Rectangular (Element => Float, Default => 0.0);
use Rect;
Map : Rect.Matrix;
...
Map(-25, 97) := 42.0;

Перераспределение базового массива было бы неприемлемо затратным, если бы оно происходило при каждом увеличении размера, поэтому пакет выделяет немного больше, чем необходимо для уменьшения перераспределения.

Образец Main непрерывно расширяет массив до тех пор, пока куча не будет исчерпана, отмечая время для каждого перераспределения. Я был приятно удивлен скоростью скомпилированного кода, перераспределение массива 1_000 X 1_000 (1_000_000 элементов) занимает всего ~5 мс: Время перераспределения

Вот результат, полученный на AMD 3960X:

Resized to  0..10, 0..10 =  1 entries in  0.000002100 s
Resized to  0..24, 0..24 =  121 entries in  0.000001500 s
Resized to  0..54, 0..54 =  625 entries in  0.000011800 s
Resized to  0..118, 0..118 =  3025 entries in  0.000033200 s
Resized to  0..254, 0..254 =  14161 entries in  0.000116400 s
Resized to  0..541, 0..541 =  65025 entries in  0.000204300 s
Resized to  0..1143, 0..1143 =  293764 entries in  0.000889200 s
Resized to  0..2400, 0..2400 =  1308736 entries in  0.004220100 s
Resized to  0..5015, 0..5015 =  5764801 entries in  0.017126300 s
Resized to  0..10439, 0..10439 =  25160256 entries in  0.072370300 s
 10000 X  10000 is  381Mb
Resized to  0..21661, 0..21661 =  108993600 entries in  0.328238800 s
 20000 X  20000 is  1525Mb
Resized to  0..44827, 0..44827 =  469242244 entries in  1.432776000 s
 30000 X  30000 is  3433Mb
 40000 X  40000 is  6103Mb
Resized to  0..92556, 0..92556 =  2009549584 entries in  56.372428000 s
 50000 X  50000 is  9536Mb
 60000 X  60000 is  13732Mb
 70000 X  70000 is  18692Mb
 80000 X  80000 is  24414Mb
 90000 X  90000 is  30899Mb

raised STORAGE_ERROR : System.Memory.Alloc: heap exhausted

Как и ожидалось, STORAGE_ERROR, у меня 32 ГБ памяти.

Это одна из моих первых попыток в Аде, критика будет очень кстати.

главная.объявления

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Assertions; use Ada.Assertions;
with Rectangular;

procedure Main is
    
    subtype Element is Float;
    
    Default_Value : Element := 42.0;
    
    package Rect is new Rectangular (Element => Element, Default => Default_Value);
    use Rect;
    Map : Rect.Matrix;
    
begin
    declare -- warmup, ensure values get set and defaults are applied
    begin
        Map (0, 0) := 2.3;
        Map (10, 10) := Map (0, 0) + 1.0;
        Assert (Map (0, 0) = 2.3);
        Assert (Map (10, 10) = 3.3);
        Assert (Map (5, 5) = Default_Value);
    end;
    
    declare -- Exercise hard to get reallocation timings
        Bytes           : Long_Long_Integer;
        MBytes          : Long_Long_Integer;
        ILong           : Long_Long_Integer;
        Current, Should : Element;
    begin
        for I in 0 .. 100_000 loop

            Map (I, I) := Element (I * 3);
            
            if I mod 10_000 = 0 then -- occasionally
                
                -- Check every value. On diagonal=3*, Off diagonal=Default_Value
                for Row in 0 .. I loop
                    for Col in 0 .. I loop
                        Current := Map (Row, Col );
                        if Row = Col then
                            Should := Element (Row * 3);
                        else
                            Should := Default_Value;
                        end if;
                        Assert (Current = Should);
                    end loop;
                end loop;
                
                -- Show progress
                ILong := Long_Long_Integer (I);
                Bytes := Ilong * Ilong * Long_Long_Integer (Element'Size) / 8;
                MBytes := Bytes / 2 ** 20;
                Put_Line (I'Image & " X " & I'Image & " is " & MBytes'Image & "Mb");
            end if;
        end loop;
    end;
end Main;

Прямоугольные.объявления

generic
    type Element is private;
    Default : Element;

package Rectangular is
-- Provides an X..Y matrix of Element, which can be used just like a 2D Array.
-- The bounds of the array adjust themselves to accomodate requested indexes.
-- Rule-of-thumb: ~5 millseconds to re-allocate an array of 1'000'000 (1'000 x 1'000) entries. YMMV.
-- Usage:
--    package Rect is new Rectangular (Element => Float, Default => 0.0);
--    use Rect;
--    Map : Rect.Matrix;
--    ...
--    Map(-25, 97) := 42.0;
-- The bounds are now -25..0, 0..97, 2'548 elements, all 0.0 except -25,97 = 42.0
    
    type Matrix is tagged limited private
      with 
         Constant_Indexing => Get_Element,
         Variable_Indexing => Get_Reference;

    type Element_Access is access all Element;

    function Get_Element (M : in out Matrix; E : in Element_Access) return Element;

    function Get_Element (M : in out Matrix; Row, Col : in Integer) return Element;

    type Reference (R : access Element) is limited null record
      with Implicit_Dereference => R;

    function Get_Reference (M : in out Matrix; E : in Element_Access) return Reference;

    function Get_Reference (M : in out Matrix; Row, Col : in Integer) return Reference;

private
    type Backing is array (Integer range <>, Integer range <>) of Element;
    type Backing_Access is access Backing;
    type Matrix is tagged limited record
        Items : Backing_Access;
    end record;
end Rectangular;

Прямоугольный.adb:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
with Ada.Strings; use Ada.Strings;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with Ada.Unchecked_Deallocation;
with Ada.Numerics.Generic_Elementary_Functions;

package body Rectangular is

    Demo : constant Boolean := True; -- Set to False once you've watched the demo
    
    function Create (Row_First, Row_Last, Col_First, Col_Last : Integer) return Backing_Access is
    -- Create a Backing array of Element'Access with (possibly negative) bounds
    begin
        return Answer :  Backing_Access :=
          new Backing (Row_First .. Row_Last, Col_First .. Col_Last)
        do
            for I in Row_First .. Row_Last loop
                for J in Col_First .. Col_Last loop
                    Answer (I, J) := Default;
                end loop;
            end loop;
        end return;
    end Create;
    
    function Multiplier (Bounds : Integer) return Float is
    -- From the bounds of an array, calculate a suitable, gentle increase
    -- Bounds | Log/(1+bounds,2) | 1/That | Increase
    --       1         1.0          1.000         1 
    --      10         3.5          0.289         3 
    --     100         6.7          0.150        15 
    --   1,000        10.0          0.100       100 
    --   5,000        12.3          0.081       407 
    --  10,000        13.3          0.075       753 
    --  25,000        14.6          0.068     1,711 
    -- 100,000        16.6          0.060     6,021 
    --
    -- So, for a matrix bound (row or column) that is 25'000, 
    -- the matrix will be resized to 25'000+1'711=26'711
    
        package Floats is new Ada.Numerics.Generic_Elementary_Functions (Float);
        Factor, Result : Float;
    begin
        Factor := Floats.Log (Float (1 + Bounds), 2.0);
        Result := 1.0 + 1.0 / Factor;
        -- Put_Line (Bounds'Image & ' ' & Factor'Image & ' ' & Result'Image);
        return Result;
    end Multiplier;
    
    procedure Free is new Ada.Unchecked_Deallocation (Backing, Backing_Access);
    -- Release a Backing.
    -- We know that this is safe, as they are private and only *we* can allocate them
    
    procedure Adjust_Bounds (M : in out Matrix; Row, Col : in Integer) is
    -- Check to see if Row-Col is within the current bounds.
    -- If not, enlarge the Backing to accomodate said Row-Col
    begin
        if M.Items = null then -- auto-initialise
            M.Items := Create (Row, Row, Col, Col);
        end if;
            
        if Row >= M.Items'First (1) and Row <= M.Items'Last (1) and
          Col >= M.Items'First (2) and Col <= M.Items'Last (2) then
            return; -- In bounds, all is well
        end if;
        
        declare
            Enlarged                       : Backing_Access;
            Row_First, Row_Last            : Integer;
            Col_First, Col_Last            : Integer;
            Row_Range, Col_Range           : Integer;
            Row_Multiplier, Col_Multiplier : Float;
            Start_Time, End_Time           : Time;
        
        begin
            if Demo then
                Start_Time := Clock;
            end if;
            Row_First := M.Items'First (1);
            Row_Last := M.Items'Last (1);
            Row_Range := Row_Last - Row_First + 1;
            Row_Multiplier := Multiplier (Row_Range);
        
            Col_First := M.Items'First (2);
            Col_Last := M.Items'Last (2);
            Col_Range := Col_Last - Col_First + 1;
            Col_Multiplier := Multiplier (Col_Range); 

            -- Integer'Min because the requested index may be further out than our conservative expansion multiplier
            if Row < Row_First then
                Row_First := Integer'Min (Row, Row_First - Integer (Float (Row_Range) * Row_Multiplier));
            elsif Row > Row_Last then
                Row_Last := Integer'Max (Row, Row_Last + Integer (Float (Row_Range) * Row_Multiplier));
            end if;
        
        
            if Col < Col_First then
                Col_First := Integer'Min (Col, Col_First - Integer (Float (Col_Range) * Col_Multiplier));
            elsif Col > Col_Last then
                Col_Last := Integer'Max (Col, Col_Last + Integer (Float (Col_Range) * Col_Multiplier));
            end if;

            Enlarged := Create (Row_First, Row_Last, Col_First, Col_Last);

            -- Copy old to new
            for R in M.Items'Range (1) loop
                for C in M.Items'Range (2) loop
                    Enlarged (R, C) := M.Items (R, C);
                end loop;
            end loop;
        
            Free (M.Items);
            M.Items := Enlarged;
            
            -- just for demonstration
            if Demo then
                declare
                    Seconds : Duration;
                    Size    : Long_Long_Integer := Long_Long_Integer (Row_Range) * Long_Long_Integer (Col_Range);
                begin
                    End_Time := Clock;
                    Seconds := End_Time - Start_Time;
                    Row_Range := Row_Last - Row_First + 1;
                    Col_Range := Col_Last - Col_First + 1;
                    Put_Line ("Resized to " & Row_First'Image & ".." & Trim (Row_Last'Image, Left) & ',' & 
                      Col_First'Image & ".." & Trim (Col_Last'Image, Left) & 
                      " = " & Size'Image & " entries in " & Duration'Image (Seconds) & " s");
                end;
            end if;
        end;
        
    end Adjust_Bounds;
    
    function Get_Reference (M : in out Matrix; E : in Element_Access) return Reference is
      (Reference'(R => E));

    function Get_Element (M : in out Matrix; E : in Element_Access) return Element is
      (M (E));

    function Get_Element (M : in out Matrix; Row, Col : in Integer) return Element is
        Result : Element;
    begin
        Adjust_Bounds (M, Row, Col);
        Result := M.Items (Row, Col);
        return Result;
    end Get_Element;

    function Get_Reference (M : in out Matrix; Row, Col : in Integer) return Reference is
    begin
        Adjust_Bounds (M, Row, Col);
        -- Unrestricted_Access is wicked, but we know what we're doing and it's the only way
        return Answer : Reference :=
          Reference'(R => M.Items ( Row, Col)'Unrestricted_Access);
    end Get_Reference;
    
end Rectangular;
person smirkingman    schedule 12.02.2021