Ada 95 Quality and Style Guide Chapter 8

Chapter 8: Reusability - TOC - 8.3 ADAPTABILITY

8.3.3 Formal Private and Limited Private Types

guideline

  • Consider using a limited private type for a generic formal type when you do not need assignment on objects of the type inside the generic body.
  • Consider using a nonlimited private type for a generic formal type when you need normal assignment on objects of the type inside the body of the generic.
  • Consider using a formal tagged type derived from Ada.Finalization.Controlled when you need to enforce special assignment semantics on objects of the type in the body of the generic.
  • Export the least restrictive type that maintains the integrity of the data and abstraction while allowing alternate implementations.
  • Consider using a limited private abstract type for generic formal types of a generic that extends a formal private tagged type.

  • example

    The first example shows a case of a template providing only a data structure, a case in which assignment is clearly not needed in the body of the generic:

    ------------------------------------------------------------------------
    generic
       type Element_Type is limited private;
    package Generic_Doubly_Linked_Lists is
       type Cell_Type;
       type List_Type is access all Element_Type;
       type Cell_Type is
          record
             Data     : Element_Type;
             Next     : List_Type;
             Previous : List_Type;
          end record;
    end Generic_Doubly_Linked_Lists;
    

    The second example shows a template that composes new operations out of (nonassignment) operations passed as generic formal parameters:

    generic
       type Element_Type is limited private;
       with procedure Process_Element (X : in out Element_Type);
       type List_Type is array (Positive range <>) of Element_Type;
    procedure Process_List (L : in out List_Type);
    procedure Process_List (L : in out List_Type) is
    begin -- Process_List
       for I in L'Range loop
          Process_Element (L(I));
       end loop;
    end Process_List;
    ------------------------------------------------------------------------
    generic
       type Domain_Type is limited private;
       type Intermediate_Type is limited private;
       type Range_Type is limited private;
       with function Left (X : Intermediate_Type) return Range_Type;
       with function Right (X : Domain_Type) return Intermediate_Type;
    function Generic_Composition (X : Domain_Type) return Range_Type;
    -- the function Left o Right
    function Generic_Composition (X : Domain_Type) return Range_Type is
    begin  -- generic_Composition
       return Left (Right (X));
    end Generic_Composition;
    

    The third example shows how to use Ada's controlled types to provide special assignment semantics:

    with Ada.Finalization;
    generic
       type Any_Element is new Ada.Finalization.Controlled with private;
       Maximum_Stack_Size : in Natural := 100;
    package Bounded_Stack is
       type Stack is private;
       procedure Push (On_Top      : in out Stack;
                       New_Element : in     Any_Element);
       procedure Pop  (From_Top    : in out Stack;
                       Top_Element :    out Any_Element);
       Overflow  : exception;
       Underflow : exception;
       ...
    private
       type Stack_Information;
       type Stack is access Stack_Information;
    end Bounded_Stack;
    

    rationale

    For a generic component to be usable in as many contexts as possible, it should minimize the assumptions that it makes about its environment and should make explicit any assumptions that are necessary. In Ada, the assumptions made by generic units can be stated explicitly by the types of the generic formal parameters. A limited private generic formal type prevents the generic unit from making any assumptions about the structure of objects of the type or about operations defined for such objects. A private (nonlimited) generic formal type allows the assumption that assignment and equality comparison operations are defined for the type. Thus, a limited private data type cannot be specified as the actual parameter for a private generic formal type.

    In general, you should choose the private or limited private generic formal type based on the need for assignment inside a generic. Limited private types should be used for abstractions that do not need assignment, as in the first two examples above. In the third example, where assignment is needed, a type derived from a controlled type is specified to ensure that the correct assignment semantics will be available. If you need equality in the body of the generic, you may need to redefine equality as well to get the correct semantics; you would then need to include a formal generic subprogram parameter for the = function.

    The situation is reversed for types exported by a reusable part. For exported types, the restrictions specified by limited and limited private are restrictions on the user of the part, not on the part itself. To provide maximum capability to the user of a reusable part, export types with as few restrictions as possible. Apply restrictions as necessary to protect the integrity of the exported data structures and the abstraction for the various implementations envisioned for that generic.

    Because they are so restrictive, limited private types are not always the best choice for types exported by a reusable part. In a case where it makes sense to allow the user to make copies of and compare data objects, and when the underlying data type does not involve access types (so that the entire data structure gets copied or compared), then it is better to export a (nonlimited) private type. In a case where it makes sense to allow the user to make copies of and compare data objects and when the underlying data type involves access types (so that the entire data structure gets copied or compared), then it is better to export a controlled type and an (overridden) equality operation. In cases where it does not detract from the abstraction to reveal even more about the type, then a nonprivate type (e.g., a numeric, enumerated, record, or array type) should be used.

    One use of generic units is to create a mixin generic (see Guideline 8.3.8) to extend a tagged type. In this situation, you want to use the most restrictive type as the generic formal type, that is, a formal type that is both limited and abstract. When you instantiate the generic, if the actual type is nonlimited, the type extension will also be nonlimited. In the generic package, you must declare the type extension as abstract. The instantiator of the generic can then extend the type again to achieve the desired mixin configuration.

    notes

    The predefined packages, Sequential_IO and Direct_IO, take private types. This will complicate I/O requirements for limited private types and should be considered during design.

    There are also some cases where you must use a limited private formal type. These cases arise when the formal type has an access discriminant, or the formal is used as the parent type in defining a type extension that itself includes a component of a limited type (e.g., task type), or the formal defines a new discriminant part with an access discriminant.


    < Previous Page Search Contents Index Next Page >
    1 2 3 4 5 6 7 8 9 10 11
    TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC
    Appendix References Bibliography