Ссылочные типы (указатели)

Введение

Указатели - потенциально опасная конструкция, которая вступает в противоречие с основополагающей философией Ады.

Есть два способа, которыми Ада помогает оградить программистов от опасности указателей:

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

  2. Во-вторых, Ада сделала указатели максимально безопасными и ограниченными, хотя допускает «аварийные люки», которые программист явно затребовать и, предположительно, будет использовать их с соответствующей осторожностью.

Вот как в Аде объявляется простой тип указателя, ссылочный тип:

package Dates is type Months is (January, February, March, April, May, June, July, August, September, October, November, December); type Date is record Day : Integer range 1 .. 31; Month : Months; Year : Integer; end record; end Dates;
with Dates; use Dates; package Access_Types is -- Declare an access type type Date_Acc is access Date; -- ^ "Designated type" -- ^ Date_Acc values point -- to Date objects D : Date_Acc := null; -- ^ Literal for -- "access to nothing" -- ^ Access to date end Access_Types;

На этом примере показано, как:

  • Объявить ссылочный тип, значения которого указывают на объекты определенного типа

  • Объявить переменную (для ссылочных значений) этого ссылочного типа

  • Присвоить ему значение null

В соответствии с философией строгой типизации Ада, если объявить второй ссылочный тип, указывающий на Date, эти два ссылочных типа будут несовместимы друг с другом:

with Dates; use Dates; package Access_Types is -- Declare an access type type Date_Acc is access Date; type Date_Acc_2 is access Date; D : Date_Acc := null; D2 : Date_Acc_2 := D; -- ^ Invalid! Different types end Access_Types;

На других языках

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

В Аде это не так, и к этому, возможно, придется какое-то время привыкать. Казалось бы, простой вопрос, если вы хотите для вашего типа иметь канонический ссылочный тип, где его объявить? Обычно используют следующий подход, если понадобится ссылочный тип для некоторого вашего типа, то вы объявите его вместе с типом:

package Access_Types is
   type Point is record
      X, Y : Natural;
   end record;

   type Point_Access is access Point;
end Access_Types;

Выделение (allocation) памяти

После того, как мы объявили ссылочный тип, нам нужен способ присвоить переменным этого типа осмысленные значения! Вы можете получить значение ссылочного типа с помощью ключевого слова new.

with Dates; use Dates; package Access_Types is type Date_Acc is access Date; D : Date_Acc := new Date; -- ^ Allocate a new Date record end Access_Types;

Если тип, память для значение которого требуется выделить, требует ограничений, их можно указать после подтипа, как в объявлении переменной:

with Dates; use Dates; package Access_Types is type String_Acc is access String; -- ^ -- Access to unconstrained array type Msg : String_Acc; -- ^ Default value is null Buffer : String_Acc := new String (1 .. 10); -- ^ Constraint required end Access_Types;

Однако, в некоторых случаях выделение памяти путем указания типа не является идеальным, поэтому Ада позволяет инициализировать объект одновременно с выделением памяти. Чтобы сделать это необходимо использовать квалифицированное выражение:

with Dates; use Dates; package Access_Types is type Date_Acc is access Date; type String_Acc is access String; D : Date_Acc := new Date'(30, November, 2011); Msg : String_Acc := new String'("Hello"); end Access_Types;

Извлечение по ссылке

Последняя часть мозайки ссылочных типов языка Ада покажет нам, как получить значение объекту по ссылке, то есть как «разыменовать» указатель. Для этого в Аде используется синтаксис .all, но часто в нем вообще нет необходимости - во многих случаях использования ссылочного значения эта операция выполняется неявно:

with Dates; use Dates; package Access_Types is type Date_Acc is access Date; D : Date_Acc := new Date'(30, November, 2011); Today : Date := D.all; -- ^ Access value dereference J : Integer := D.Day; -- ^ Implicit dereference for -- record and array components -- Equivalent to D.all.day end Access_Types;

Другие особенности

Как вы, возможно, заметили, если пользовались указатели в C или C++, мы не показали некоторых функций, которые считаются основополагающими при использования указателей, таких как:

  • Арифметика над указателями (возможность увеличивать или уменьшать указатель, чтобы переместить его на следующий или предыдущий объект)

  • Освобождение памяти вручную - то, что в C делается с помощью free или delete. Это потенциально опасная операция. Чтобы оставаться в безопасном пространстве Ады, вам лучше не освобождать память вручную.

Эти функции существуют в Аде, но воспользоваться ими можно только с помощью определенных интерфейсов стандартной библиотеки (API).

Attention

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

Существует множество способов избежать распределения памяти вручную, с некоторыми из них мы уже встречались (например, виды параметров). Язык также предоставляет библиотечные абстракции, чтобы избежать указателей:

  1. Одним из них является использование контейнеров. Контейнеры помогают пользователям избегать указателей, поскольку сами управляют памятью.

  2. Контейнер, который следует отметить в этом контексте, является Indefinite holder. Этот контейнер позволяет хранить значение неопределенного типа, например String.

  3. GNATCOLL🤢 имеет библиотеку для интеллектуальных указателей, называемую Refcount, память этих указателей автоматически управляется, так что, когда у выделенного объекта больше нет ссылок на него, память автоматически освобождается.

Взаимно рекурсивные типы

Связанный список является широко известной идиомой в программировании; в Аде его наиболее естественная запись включает определение двух типов - тип записи и ссфлочный тип, которые будут взаимно зависимыми. Для объявления взаимно зависимых типов можно использовать неполное объявление типа:

package Simple_List is type Node; -- This is an incomplete type declaration, -- which is completed in the same -- declarative region. type Node_Acc is access Node; type Node is record Content : Natural; Prev, Next : Node_Acc; end record; end Simple_List;