oopadadynamic-dispatch

When do I use a predefined class wide access type vs. an anonymous access to a class wide type as a parameter?


I encountered the use of predefined class wide access types as the main parameter to a subprogram many times in our code base and would like to know whether it's a flaw in the design or if it has some purpose. Let me first explain what I encountered and what I would like to change:

Let's say I have some tagged type A with a child B extending A:

Package A_Types:

type A is tagged null record;

function Get_Name (This : A) return String
   is ("A");

Package B_Types:

type B is new A with null record;

function Get_Name (This : B) return String
   is ("B");

Now let's say I'm writing some utility package with a function Show_Name that uses a class wide access:

Package Utilities:

procedure Print_Name (Instance : access A'Class) is
begin
   Putline ("Name: " & Instance.Get_Name);
end Print_Name;

This will dispatch Get_Name to the right type of Instance.

However, what I often encounter in our code base is that they define this utility package as:

procedure Print_Name (Instance : A_Cwa) is
begin
   Putline ("Name: " & Instance.Get_Name);
end Print_Name;

With A_Cwa defined in the A_Types package as:

type A_Cwa is access all A'Class;

This can of course be called on some instance that's stored somewhere as A_Cwa, but the moment I want to call this procedure on some access to a type in A'Class, it requires me to first make a view conversion to A_Cwa before I can call the procedure on it.

Now, in many places we're calling some procedure on an A_Cwa instance. This would look something like:

Show_Info (Some_Store.Get_The_Instance);

with Show_Info defined in the A_Types package as:

procedure Show_Info (Instance : A_Cwa) is
begin
   Utilities.Print_Name (Instance);
end Show_Info;

Since Show_Info is defined in the A_Types package and it seems to do something that looks primitive to the A'Class types, I'd like to write:

Some_Store.Get_The_Instance.Show_Info;

But to make it a primitive I have to change Show_Info into:

procedure Show_Info (This : access A) is
begin
   Utilities.Print_Name (A_Cwa (This));
end Show_Info;

I don't like the view conversion here, so to fix this I can change Print_Name into:

procedure Print_Name (Instance : access A'Class) is
begin
   Putline ("Name: " & Instance.Get_Name);
end Print_Name;

This works, so it makes me wonder: why use the predefined class wide access type A_Cwa as a parameter in the first place? It seems to prevent the use of the utility procedure on any instance of A unless it's stored as an A_Cwa type.

Was the initial design wrong, or is there a good reason why to use the A_Cwa type instead of the anonymous access A'Class in the signature of the Print_Name utility?

Disclaimer: code in this question was not tested, but analog to what I encountered in our code base. This question is more about the design than it's about fixing some bug.


Solution

  • It's hard to give solid advise without having the complete picture of the software upon which the example is based, but as @JeffreyR.Carter states in his comment: it's best to avoid anonymous access types as much as possible because of the potentially required run-time accessibility checks and their somewhat unexpected failures during run-time (unexpected in the sense that it's sometimes hard to predict beforehand whether a check will fail or not due the complex accessibility rules).

    However, going back to the (limited) example, it seems reasonable to make Show_Info a primitive of A, but it would also make sense to avoid the use of access types altogether. Tagged types are passed by reference (RM 6.2 (5)), so the number of situations where you need to reference a tagged type explicitly (using some access type) are limited to those in which an explicit assignment must not cause an object to be copied.