To help you see how these different capabilities can work together, I've built a sample package combining these different ideas. You don't need to understand every little nuance at this time, though I've given you enough information to do so. Concentrate on understanding the big picture the first time you read this.
The sample package I've developed is a generic stack package, similar to the example we saw in the section on generics. Now, however, we can use access types to implement an unbounded stack - we no longer need to be limited to any particular size. We will want to permit users to assign stacks to other stacks, so we will use "Ada.Finalization" and implement type Stack as a child of type Controlled so we can control how assignment and finalization occurs (we discussed type Controlled in the last section of lesson 7).
We will have push and pop operations as before. Let's add an "Empty" operation to erase the contents of a stack, and a boolean function "Is_Empty" that will return True if there's no data in the stack. We'll also add an "=" operation that will return True if two stacks are equal (two stacks are equal if they have the same length and contain equal data in the same order).
I recommend that you always consider adding a "swap" operation to reusable data types [Wheeler 1992]; note that swap operation can be implemented very efficiently using access types. A "Length" operation is also defined so you can find how many items are in the stack. Note there's a new type, "Natural". Natural is a predefined subtype of Integer that starts at zero. Since we can't have a negative number of objects on a stack, it's more appropriate to return a Natural than to return an Integer.
When you're implementing an unbounded type you should almost always override the default Adjust and Finalize procedures - if you don't you're probably doing something wrong. If we didn't override Adjust, an assignment would cause two "different" stacks to point to the same data nodes. As a result, any later finalizing we did would affect other stacks, even if they shouldn't. If we didn't override Finalize, we wouldn't Free the data nodes that we should.
Given all that, here is a generic package specification for Generic_Stack:
Note the tricky thing that was done here - the node isn't even completely defined in the package! Since the node wasn't passed in or out of anything, we can leave it as an incomplete type definition and complete the definition in the generic package body.
Since it's a generic, we have to instantiate the generic with a specific type to use it. For test purposes, let's instantiate the generic to allow us to stack up Integers:
And finally, let's write a short test program to demonstrate using the generic stack:
The generic package body implements the operations defined by the generic package declaration using access types as we've discussed. Feel free to examine the package body of Generic_Stack, and compare it to its package specification, a sample instantiation (that creates a Stack of Integers), another instantiation (creating a Stack of Stacks of Integers), a short demonstration program, and a longer test program that puts the Stack through its paces.
Can customers of the generic stack package defined above use the Stack_Node_Access type and manipulate the internal structure of the Generic_Stack?
|Go back to the previous section||Skip to the next section||Go up to lesson 12 outline|
David A. Wheeler (email@example.com)