【问题标题】:Missing features of Ada recordAda 记录的缺失特征
【发布时间】:2014-12-09 15:24:51
【问题描述】:

Consider following C++ code:

class Person
{
public:
    const std::string Name;
    int Weight = 0;
    Person(std::string AssignName) : Name(AssignName)
    {}
};

void Dinner(Person & ModifyPerson)
{
    ModifyPerson.Weight += 2;
}

Name 是常量,它在创建 Person 时首先被初始化。 重量是可以修改的。

Person Person0("Conny");
Dinner(Person0);

让我们在 Ada 中尝试一下:

type Person is record
    Name : constant String;
    Weight : Integer := 0;
end record;

Person0 : Person := Person'(Name => "Abby");

Person 的名称应该始终是“Abby”,因为它的常量和 Weight 应该是可修改的,并且默认为 0。

不起作用,因为 Ada 记录不允许以下操作:

  • 常量字段
  • 未指定字段类型的范围
  • 限制初始化列表,即只分配 Name 并且默认 Weight = 0。

如何在Ada代码中做C++代码?

在 C++ 中,我可以通过派生来扩展 Person。 在 Ada 中,我们为此使用标记记录。 但是标记记录也不起作用。

如何在 Ada 代码中使用 Person 的可扩展功能编写 C++ 代码?

【问题讨论】:

  • 您使用了std::string,这避免了指定范围的需要。 Ada 等价物是Unbounded_String。我一点也不了解 C++,但我怀疑如果你声明 Name : char[];,你会遇到问题。

标签: c++ initialization constants record ada


【解决方案1】:

在 Ada 中,您首先决定 Person 能做什么和不能做什么,然后设计界面。稍后实施。

据我所知,您希望 Person 能够:

  1. 有一个不可变的名字(婚姻可能是个问题,但没关系)
  2. 具有可变权重,初始化为 0
  3. 有增重或减重的能力。
  4. 根据讨论,可扩展(可子类化)

现在,Ada 记录就像 Struct,但我们想要的不止于此,将数据类型及其操作包装在一起的常用方法是 Package。 (这也与 C++ 命名空间有一些共同点,它也使主命名空间保持整洁)

Package Person_Pack is
  -- tagged for extensibility and other goodies
  type Person (Name_Length : Natural) is tagged private;
  function Name (P:Person) return String;
  function Weight (P:Person) return Natural;
  procedure Gain_Weight (P: in out Person; Increment : Integer);
  -- Explicit constructor, like "object factory" pattern
  function Birth(Name : String) return Person;
private
  -- none of your business yet...
end Person_Pack;

这就是界面(另存为“person_pack.ads”)。 Name有getter方法但没有setter方法,我们保证Person.Name的恒常性。

私有部分包含了类型声明的细节,这样客户端代码就可以为Person分配空间,基本上没有其他原因。所以,除了公共界面,不要摆弄记录字段......我认为我不需要说明为什么这是一件好事?所以私处可能看起来像

private
  type Person (Name_Length : Natural) is tagged record
    Name   : String(1 .. Name_Length);
    Weight : Integer := 0;
  end record;
end Person_Pack;

这是一个有区别的记录,因此我们可以将名称直接存储在记录中,而不是使用 Ada.Containers(最接近 STL 的等价物)甚至 Unbounded_String。这是一个实现细节,因为它仅限于私有部分,如果我们稍后更改它而不更改接口并重新编译,客户端代码仍然可以工作。

实现在包体“person_pack.adb”中。

Package Body Person_Pack is
  function Name (P:Person) return String is
  begin
    return P.Name;
  end Name;

  function Weight (P:Person) return Natural is
  begin
    return P.Weight;
  end Weight;

  procedure Gain_Weight (P: in out Person; Increment : Integer) is
  begin
    P.Weight := P.Weight + Increment;
  end Gain_Weight;

  function Birth(Name : String) return Person is
    baby : Person(Name'Length);
  begin
    baby.Name := Name;
    baby.Weight := 0;
    return baby;
  end Birth;

end Person_Pack;

用法(另存为main.adb):

with Person_Pack; use Person_Pack;

procedure Main is

  -- type extension : refactor this into another package...
  type Employee is new Person with
    record
      Salary : Integer;
    end record;

  function Birth(Name : String) return Employee is
    baby : Person(Name'Length) := Birth(Name);
  begin
    return (baby with Salary => 0);
  end Birth;

  Abby : Person := Birth("Abigail");
  John : Employee := Birth("John");

  procedure Dinner (Gourmand : in out Person) is
  begin
    Gourmand.Gain_Weight(2);
  end Dinner;

begin
  Dinner(Abby);
end Main;

编译:

gcc -c -gnat2012 main.adb
gcc -c -gnat2012 person_pack.adb
gnatbind -x main.ali; gnatlink main.ali

【讨论】:

  • Procedure Dinner 应该无法编译,因为 Gourmand 只是一个 in 参数。
  • 似乎 Person 应该在公共部分用未知的判别式声明(给定它的私有定义)。这也将强制用户调用 Birth 来创建新用户,从而确保正确初始化 Name。
【解决方案2】:

公共数据成员在任何面向对象的语言中都是不受欢迎的。因此,即使在 C++ 中,我希望具有扎实设计背景的人会告诉您将 Name 和 Weight 放入 private: 部分,并添加 getter 和 setter 方法来检索和设置字段。当然,您不会有 Name 的设置器,因为它是不可更改的。

在 Ada 中也是如此。将record 设为私有:

    type Person is private;  --or
    type Person is tagged private;

    function Make_Person (Name : String; Weight : Integer := 0) return Person;

    function Get_Name (X : Person) return String;

    function Get_Weight (X : Person) return Integer;
    procedure Set_Weight (X : in out Person; Weight : Integer);

private

    type Person is record --or
    type Person is tagged record 
        Name : Ada.Strings.Unbounded.Unbounded_String;
        Weight : Integer;
    end record;

Unbounded_String 是处理长度可以动态变化的字符串变量或记录字段的常用方法。有关如何使用此包以及如何在Unbounded_String 和String 类型之间转换的信息,请参阅RM A.4.5。这些转换只会出现在定义Person 的包的主体中;使用Person 的客户端包不必担心。

如果您将Person 设为tagged record,您仍然可以使用“点”符号来访问函数:

P : Person;

...

Name : String := P.Get_Name;       -- calls the getter function
Weight : Integer := P.Get_Weight;  -- calls the getter function

P.Set_Weight (P.Get_Weight + 2);

我无法回应你关于“标记记录也不起作用”的说法,因为你没有提供任何细节。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-16
    • 1970-01-01
    相关资源
    最近更新 更多