Создание объектов с помощью примитивной операции

#ada

#ada

Вопрос:

Мой вопрос содержит более длинный фрагмент кода Ada. В основном я пытался смоделировать сообщение с заголовком объектно-ориентированным способом. Две примитивные операции Create_Message и Create_Message_Access могут быть использованы для создания конкретных объектов сообщений. Я не совсем уверен, должна ли примитивная операция возвращать тип Message_Type или Message_Type_Access . Какой тип рекомендуется (эффективность?) или оба решения не являются оптимальными?

Я думаю, что первым способом объект создается в стеке, а затем копируется после выполнения return инструкции, потому что переменная Object выходит за пределы области видимости. Правильно или неправильно?

 function Create_Message (Title : String) return Message_Type is
   Object : Message_Type;
begin
   Object.Title := To_Unbounded_String (Title);

   return Object;
end Create_Message;
  

Я думаю, что вторым способом объект создается в куче, а затем указатель копируется после выполнения return инструкции, потому что переменная Object выходит за пределы области видимости. Правильно или неправильно?

 function Create_Message_Access (Title : String) return Message_Type_Access is
   Object : Message_Type_Access := new Message_Type;
begin
   Object.Title := To_Unbounded_String (Title);

   return Object;
end Create_Message_Access;
  

Позже я создал пример объекта с заголовком, использовал примитивную операцию Updated_Title для изменения заголовка и вернул его обратно в конце.

 First_Message := Message.Create_Message ("First");

Ada.Text_IO.Put_Line (First_Message.Get_Title);

First_Message.Update_Title ("First changed");

Ada.Text_IO.Put_Line (First_Message.Get_Title);

First_Message.Update_Title ("First");

Ada.Text_IO.Put_Line (First_Message.Get_Title); 
  

Результат (как и ожидалось):

 First
First changed
First
  

Затем я сохранил сообщение в векторном контейнере и попытался изменить заголовок, выполняя итерацию по элементам, сохраненным в векторе. Я обнаружил, что заголовок сохраняет старое значение, поэтому я предполагаю, что вызов операции Message_Vector.Element (Message_Cursor) возвращает только копию сообщения, хранящегося в векторе. Правильно или неправильно?

 Messages.Append (First_Message);

Message_Cursor := Message_Vector.First (Messages);

while Message_Vector.Has_Element (Message_Cursor) loop
   declare
      Temporary_Message : Message.Message_Type;
   begin
      Temporary_Message := Message_Vector.Element (Message_Cursor);
      Temporary_Message.Update_Title ("First changed");
   end;

   Message_Vector.Next (Message_Cursor);
end loop;

Message_Cursor := Message_Vector.First (Messages);

while Message_Vector.Has_Element (Message_Cursor) loop
   --
   -- Prints "First" and not "First changed"
   --
   Ada.Text_IO.Put_Line (Message_Vector.Element (Message_Cursor).Get_Title);

   Message_Vector.Next (Message_Cursor);
end loop;
  

Последний вопрос: В чем разница между Second_Message.all.Get_Title и Second_Message.Get_Title ?

 Second_Message := Message.Create_Message_Access ("Second");

Ada.Text_IO.Put_Line (Second_Message.all.Get_Title);
Ada.Text_IO.Put_Line (Second_Message.Get_Title);
  

Полный исходный код:

 with Ada.Containers.Vectors;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

with Ada.Text_IO;

procedure Main is

   package Message is

      type Message_Type is tagged private;

      type Message_Type_Access is access Message_Type;

      function Create_Message (Title : String) return Message_Type;

      function Create_Message_Access (Title : String) return Message_Type_Access;

      function Get_Title (Self : Message_Type) return String;

      procedure Update_Title (Self : in out Message_Type; Title : String);

      function "=" (Left, Right : Message_Type) return Boolean;

   private

      type Message_Type is tagged record
         Title : Unbounded_String;
      end record;

   end Message;

   package body Message is

      function Create_Message (Title : String) return Message_Type is
         Object : Message_Type;
      begin
         Object.Title := To_Unbounded_String (Title);

         return Object;
      end Create_Message;

      function Create_Message_Access (Title : String) return Message_Type_Access is
         Object : Message_Type_Access := new Message_Type;
      begin
         Object.Title := To_Unbounded_String (Title);

         return Object;
      end Create_Message_Access;

      function Get_Title (Self : Message_Type) return String is
      begin
         return To_String (Self.Title);
      end Get_Title;

      procedure Update_Title (Self : in out Message_Type; Title : String) is
      begin
         Self.Title := To_Unbounded_String (Title);
      end Update_Title;

      function "=" (Left, Right : Message_Type) return Boolean is
      begin
         return Left.Title = Right.Title;
      end "=";

   end Message;

   package Message_Vector is new Ada.Containers.Vectors (Index_Type => Natural,
      Element_Type => Message.Message_Type, "=" => Message."=");

   package Message_Access_Vector is new Ada.Containers.Vectors (Index_Type => Natural,
      Element_Type => Message.Message_Type_Access, "=" => Message."=");

   Messages       : Message_Vector.Vector;
   Message_Cursor : Message_Vector.Cursor;

   Messages_Access       : Message_Access_Vector.Vector;
   Message_Access_Cursor : Message_Access_Vector.Cursor;

   First_Message  : Message.Message_Type;
   Second_Message : Message.Message_Type_Access;

begin
   First_Message := Message.Create_Message ("First");

   Ada.Text_IO.Put_Line (First_Message.Get_Title);

   First_Message.Update_Title ("First changed");

   Ada.Text_IO.Put_Line (First_Message.Get_Title);

   First_Message.Update_Title ("First");

   Ada.Text_IO.Put_Line (First_Message.Get_Title);

   --

   Ada.Text_IO.New_Line;

   Messages.Append (First_Message);

   Message_Cursor := Message_Vector.First (Messages);

   while Message_Vector.Has_Element (Message_Cursor) loop
      declare
         Temporary_Message : Message.Message_Type;
      begin
         Temporary_Message := Message_Vector.Element (Message_Cursor);
         Temporary_Message.Update_Title ("First changed");
      end;

      Message_Vector.Next (Message_Cursor);
   end loop;

   Message_Cursor := Message_Vector.First (Messages);

   while Message_Vector.Has_Element (Message_Cursor) loop
      --
      -- Prints "First" and not "First changed"
      --
      Ada.Text_IO.Put_Line (Message_Vector.Element (Message_Cursor).Get_Title);

      Message_Vector.Next (Message_Cursor);
   end loop;

   --

   Ada.Text_IO.New_Line;

   Second_Message := Message.Create_Message_Access ("Second");

   Ada.Text_IO.Put_Line (Second_Message.all.Get_Title);
   Ada.Text_IO.Put_Line (Second_Message.Get_Title);

   --

   Ada.Text_IO.New_Line;

   Messages_Access.Append (Second_Message);

   Message_Access_Cursor := Message_Access_Vector.First (Messages_Access);

   while Message_Access_Vector.Has_Element (Message_Access_Cursor) loop
      declare
         Temporary_Message : Message.Message_Type_Access;
      begin
         Temporary_Message := Message_Access_Vector.Element (Message_Access_Cursor);
         Temporary_Message.Update_Title ("Second changed");
      end;

      Message_Access_Vector.Next (Message_Access_Cursor);
   end loop;

   Message_Access_Cursor := Message_Access_Vector.First (Messages_Access);

   while Message_Access_Vector.Has_Element (Message_Access_Cursor) loop
      Ada.Text_IO.Put_Line (Message_Access_Vector.Element (Message_Access_Cursor).Get_Title);

      Message_Access_Vector.Next (Message_Access_Cursor);
   end loop;

end Main;
  

Ответ №1:

Две примитивные операции Create_Message и Create_Message_Access могут быть использованы для создания конкретных объектов сообщений. Я не совсем уверен, должна ли примитивная операция возвращать тип Message_Type или Message_Type_Access. Какой тип рекомендуется (эффективность?) или оба решения не являются оптимальными?

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

Я думаю, что первым способом объект создается в стеке, а затем копируется после выполнения инструкции return, потому что переменный объект выходит за пределы области видимости. Правильно или неправильно?

Я думаю, это было бы скопировано во время инструкции return, но помимо этого, да (есть обстоятельства, связанные с ограниченными типами, когда вы можете «создать объект на месте», но правила изменились между стандартными выпусками языка, поэтому я не уверен; смотрите AARM 7.6 (17.1) и приготовьтесь к недоумению).

Я думаю, что вторым способом объект создается в куче, а затем указатель копируется после выполнения инструкции return, потому что переменный объект выходит за пределы области видимости. Правильно или неправильно?

Опять же, да.

… Я предполагаю вызов операции Message_Vector.Элемент (Message_Cursor) возвращает только копию сообщения, хранящегося в векторе. Правильно или неправильно?

ДА.

Последний вопрос: В чем разница между Second_Message.all.Get_Title и Second_Message.Get_Title?

В данном случае нет. Но если бы процедура была без параметров, вам нужно было бы использовать первую форму, потому что это явно не было бы вызовом подпрограммы.


Операции, подобные Element , действительно возвращают копию. Если вы хотите обновить элемент в контейнере, вы могли бы взять копию, изменить ее, а затем использовать Replace_Element для перезаписи оригинала.

Вероятно, вы сочли бы это менее громоздким (но и только) в использовании Update_Element :

 with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Vectors;
procedure Updating_Messages is
   type Message is record
      N : Integer := 0;
   end record;
   package Message_Vectors is
     new Ada.Containers.Vectors (Index_Type => Positive,
                                 Element_Type => Message);
   Messages : Message_Vectors.Vector;
begin
   Messages.Append (Message'(N => 21));                                     -- sorry about this, failing of Google syntax highlighting '
   Messages.Append (Message'(N => 42));                                     --'
   for Cursor in Messages.Iterate loop
      declare
         procedure Increment (It : in out Message) is
         begin
            It.N := It.N   1;
         end Increment;
      begin
         Messages.Update_Element (Cursor, Increment'Access);                --'
      end;
   end loop;
   for M of Messages loop  -- these are copies
      Ada.Text_IO.Put_Line (M.N'Image);                                     --'
   end loop;
end Updating_Messages;
  

Комментарии:

1. Если я решу использовать только указатели: type Message_Type (<>) is tagged limited private; Было бы полезным определением для типа?

2. Да, это то, что я сделал. Конечно (!), в конечном итоге вам приходится каким-то образом управлять освобождением (возможно, через Ada.Finalization ).

3. Если я объявляю тип для своих сообщений как type Message_Type (<>) is tagged limited : Давайте представим, что я не заинтересован в предоставлении Create функции возвращаемого типа Create_Message_Access . Может ли процедура в другом пакете принять тип Message_Type в качестве параметра, а затем создать копию этого сообщения в переменную с access типом, чтобы скопированное сообщение могло быть сохранено в vector ( Message_Access_Vector )?

4. Если вы хотите скопировать сообщение, вы можете либо не создавать его, limited либо предоставить Copy подпрограмму в первом пакете (тогда это было бы другое сообщение с тем же содержимым, предположительно; это может быть нецелесообразно в вашей системе).