One approach to storage management is to avoid the problem—i.e. to refrain from using dynamic allocation—rather than attempt to solve it. In simple applications, the program can declare a fixed number of aliased data objects and manipulate them via access values. The full power of inheritance, polymorphism, and dynamic dispatch can still be used, and the absence of dynamic allocation can be checked and enforced via Restrictions pragmas.
Another option is to allow dynamic allocation only during program initialization. Again, full OOP may be used to process the objects thus created, with a check that no objects are created after the initialization phase.
In more general OOP applications, the use of dynamic allocation is unavoidable. The challenge becomes how to mitigate its vulnerabilities, guaranteeing that no allocation request fails, no object is de-allocated while it is still “live” (referenceable from the program) and each allocation and de-allocation operation takes a fixed amount of time.
Rather than attempting to address these issues using a one-size-fits-all technique such as garbage collection, Ada’s definitional facilities enable the application programmer to compose an appropriate solution. One of the most useful features is the storage pool mechanism, which allows the programmer to define the behavior of an access type’s allocation and de-allocation operations. A storage pool consisting of fixed-size blocks can be used for allocating objects from one or more access types, for example. Fixed-size blocks eliminate fragmentation and standard algorithms ensure that allocation and de-allocation take constant time. Program analysis is still needed to verify that the pool size is sufficient and that de-allocation does not create dangling references, but static analysis tools can assist here.
Inheritance, Substitutability, and Dynamic Dispatch
One of the techniques for local type consistency verification6 is formal demonstration of the Liskov Substitution Principle (LSP)7, which means proving that a subclass’s version of an operation does not have stronger preconditions or weaker post conditions than the superclass’s version. Ada 2012 directly supports this approach through a facility known as contract-based programming. Contract-based programming includes a number of features:
Preconditions and post conditions for a subprogram. When enabled, preconditions are checked on subprogram entry, and post conditions are checked on subprogram return. Failure of any of these checks raises an exception.
An invariant condition that holds for private types, including tagged private types. An invariant is treated as a post conditions of any operation that returns a value of the type, through a function result or parameter. An invariant for a tagged type can be specified as inheritable; if a derived type explicitly specifies an invariant, then this is and-ed with the inherited invariant.
New kinds of expressions, which make it easier to express logical conditions and invariants
-Conditional expressions (if and case)
-Quantification forms, both universal and existential, for elements in a discrete range, an array, or a container data structure
Expression functions, which allow a function declaration in a package specification to include a parenthetical expression as its body. Expression functions are especially useful for functions that express preconditions, post conditions, or invariants, since often the implementation of such functions is in effect part of the package’s interface to other units.