Primitive subprograms in Ada are basically the subprograms that are eligible for inheritance / derivation. They are the equivalent of C++ member functions and Java instance methods. While in C++ and Java these subprograms are located within the nested scope of the type, in Ada they are simply declared in the same scope as the type. There's no syntactic indication that a subprogram is a primitive of a type.

The way to determine whether P is a primitive of a type T is if (1) it is declared in the same scope as T, and (2) it contains at least one parameter of type T, or returns a result of type T.

In C++ or Java, the self reference this is implicitly declared. It may need to be explicitly stated in certain situations, but usually it's omitted. In Ada the self-reference, called the controlling parameter, must be explicitly specified in the subprogram parameter list. While it can be any parameter in the profile with any name, we'll focus on the typical case where the first parameter is used as the self parameter. Having the controlling parameter listed first also enables the use of OOP prefix notation which is convenient.

A class in C++ or Java corresponds to a tagged type in Ada. Here's an example of the declaration of an Ada tagged type with two parameters and some dispatching and non-dispatching primitives, with equivalent examples in C++ and Java:

Despite the syntactic differences, derivation in Ada is similar to derivation (inheritance) in C++ or Java. For example, here is a type hierarchy where a child class overrides a method and adds a new method:

Like Java, Ada primitives on tagged types are always subject to dispatching; there is no need to mark them virtual. Also like Java, there's an optional keyword overriding to ensure that a method is indeed overriding something from the parent type.

Unlike many other OOP languages, Ada differentiates between a reference to a specific tagged type, and a reference to an entire tagged type hierarchy. While Root is used to mean a specific type, Root'Class---a class-wide type---refers to either that type or any of its descendants. A method using a parameter of such a type cannot be overridden, and must be passed a parameter whose type is of any of Root's descendants (including Root itself).

Next, we'll take a look at how each language finds the appropriate method to call within an OO class hierarchy; that is, their dispatching rules. In Java, calls to non-private instance methods are always dispatching. The only case where static selection of an instance method is possible is when calling from a method to the super version.

In C++, by default, calls to virtual methods are always dispatching. One common mistake is to use a by-copy parameter hoping that dispatching will reach the real object. For example:

voidproc(Rootp){p.method1();}Root*v=newChild();proc(*v);

In the above code, p.method1() will not dispatch. The call to proc makes a copy of the Root part of v, so inside proc, p.method1() refers to the method1() of the root object. The intended behavior may be specified by using a reference instead of a copy:

voidproc(Root&p){p.method1();}Root*v=newChild();proc(*v);

In Ada, tagged types are always passed by reference but dispatching only occurs on class-wide types. The following Ada code is equivalent to the latter C++ example:

Dispatching from within primitives can get tricky. Let's consider a call to Method_1 in the implementation of Method_2. The first implementation that might come to mind is:

procedureMethod_2(P: Root)isbeginP.Method_1;end;

However, Method_2 is called with a parameter that is of the definite type Root. More precisely, it is a definite view of a child. So, this call is not dispatching; it will always call Method_1 of Root even if the object passed is a child of Root. To fix this, a view conversion is necessary:

procedureMethod_2(P: Root)isbeginRoot'Class(P).Method_1;end;

This is called "redispatching." Be careful, because this is the most common mistake made in Ada when using OOP. In addition, it's possible to convert from a class wide view to a definite view, and to select a given primitive, like in C++:

In the declaration of V1, T.F receives a value computed by the subprogram Compute_Default_F. This is part of the default initialization. V2 is initialized manually and thus will not use the default initialization.

For additional expressive power, Ada provides a type called Ada.Finalization.Controlled from which you can derive your own type. Then, by overriding the Initialize procedure you can create a constructor for the type:

Again, this default initialization subprogram is only called for V1; V2 is initialized manually. Furthermore, unlike a C++ or Java constructor, Initialize is a normal subprogram and does not perform any additional initialization such as calling the parent's initialization routines.

When deriving from Controlled, it's also possible to override the subprogram Finalize, which is like a destructor and is called for object finalization. Like Initialize, this is a regular subprogram. Do not expect any other finalizers to be automatically invoked for you.

Controlled types also provide functionality that essentially allows overriding the meaning of the assignment operation, and are useful for defining types that manage their own storage reclamation (for example, implementing a reference count reclamation strategy).

The C++ and Java code's use of protected and the Ada code's use of private here demonstrates how to map these concepts between languages. Indeed, the private part of an Ada child package would have visibility of the private part of its parents, mimicking the notion of protected. Only entities declared in the package body are completely isolated from access.

Ada, C++ and Java all offer similar functionality in terms of abstract classes, or pure virtual classes. It is necessary in Ada and Java to explicitly specify whether a tagged type or class is abstract, whereas in C++ the presence of a pure virtual function implicitly makes the class an abstract base class. For example:

All abstract methods must be implemented when implementing a concrete type based on an abstract type.

Ada doesn't offer multiple inheritance the way C++ does, but it does support a Java-like notion of interfaces. An interface is like a C++ pure virtual class with no attributes and only abstract members. While an Ada tagged type can inherit from at most one tagged type, it may implement multiple interfaces. For example:

Any private type in Ada may be associated with a Type_Invariant contract. An invariant is a property of a type that must always be true after the return from of any of its primitive subprograms. (The invariant might not be maintained during the execution of the primitive subprograms, but will be true after the return.) Let's take the following example:

The Is_Sorted function checks that the type stays consistent. It will be called at the exit of every primitive above. It is permissible if the conditions of the invariant aren't met during execution of the primitive. In To_Int_List for example, if the source array is not in sorted order, the invariant will not be satisfied at the "begin", but it will be checked at the end.