7

If I understand correctly, the interface section is visible to other units, and the implementation section is visible only in the current .pas file.

I have two classes, class TA should be visible to the outside, other class TB should not, but I need a field of type TB in TA.

interface
    
type
  TA = class
    //something
    B : TB;
  end;
    
//something
    
implementation
    
type
  TB = class
    //something
  end;

It doesn't work like that. I also cannot use a forward declaration. Is there a way?

Or, is there a way to declare TB in the interface section but make it kind-of private?

3
  • Why does TA need a field that refers to a type that hasn't been defined yet? What is the goal here? Commented Mar 24, 2021 at 22:51
  • Do you have a use-case in mind for this?
    – MartynA
    Commented Mar 25, 2021 at 9:21
  • If you refer to TB in the interface section then you open it up to the public. It must be known in the interface. Use @RemyLebeau interface solution. In this case you just open up the interface, not its implementation.
    – The Bitman
    Commented Mar 25, 2021 at 15:14

5 Answers 5

6

A type cannot be used before it is declared (in terms of line numbers). In particular, this means that you cannot use a type declared in the implementation section in the interface section.

However, consider the following example:

unit VisibilityTest;

interface

type
  TFrog = class
  strict private type
    TFrogMetabolism = class
      procedure DoAnabolismStuff;
      procedure DoCatabolismStuff;
    end;
  strict private
    FMetabolism: TFrogMetabolism;
  public
    procedure Croak;
    procedure Walk;
    procedure Jump;
  end;

implementation

{ TFrog.TFrogMetabolism }

procedure TFrog.TFrogMetabolism.DoAnabolismStuff;
begin

end;

procedure TFrog.TFrogMetabolism.DoCatabolismStuff;
begin

end;

{ TFrog }

procedure TFrog.Jump;
begin

end;

procedure TFrog.Croak;
begin

end;

procedure TFrog.Walk;
begin

end;

end.

Here the TFrog class is visible to other units, as well as its Croak, Walk, and Jump methods.

And it does have a (strict private in this example) field of type TFrogMetabolism, a type which can only be used inside TFrog -- and therefore only inside this unit -- because of the preceding strict private specification.

This should give you some ideas. A few variants are possible:

  • If you remove strict from strict private type, the TFrogMetabolism class can be used everywhere inside this particular unit, and not only in TFrog.

  • If you replace private with protected, the class can also be used in classes that aren't TFrog but are derived from TFrog.

1
  • Of course, in your particular situation, it might or might not make sense (semantically) to use a nested type. Commented Mar 24, 2021 at 22:33
5

You can do it but with a price. In class TA, the variable referring to TB must be of type TObject. Let's name that variable B. You can assign an instance of class TB to the variable, for example from the constructor. Then when code in TA needs to use variable B, it must cast is to TB (Hard cast or use "As" operator).

You should also disable RTTI on that TB so that the outside cannot discover what is inside TB.

Here is the code:

unit Unit24;

interface

uses
    System.SysUtils;

type
    TA = class
        B : TObject;  // Will contain a TB instance
        constructor Create;
        destructor Destroy; override;
        procedure Demo;
    end;

implementation

type
    TB = class
        procedure SayHello;
    end;

{ TA }

constructor TA.Create;
begin
    inherited Create;
    B := TB.Create;
end;

procedure TA.Demo;
begin
    TB(B).SayHello;
end;

destructor TA.Destroy;
begin
    FreeAndNil(B);
    inherited Destroy;
end;

{ TB }

procedure TB.SayHello;
begin
    WriteLn('Hello!');
end;

end.

An example of use:

program Project24;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Unit24 in 'Unit24.pas';

var
  A : TA;
begin
  A := TA.Create;
  try
      A.Demo;
  finally
      A.Free;
  end;
end.
3
  • Yes, I must admit that I do things like this occasionally. But I always try to avoid it because you don't want to abandon type safety unnecessarily. Commented Mar 25, 2021 at 8:27
  • @AndreasRejbrand You don't completely abandon type safety provided - as I suggested in the post - you use the "as" operator. WinApi is full of similar constructions. Many "handles" are actually pointer to unexposed structure.
    – fpiette
    Commented Mar 25, 2021 at 9:28
  • Sorry, I mean "compile-time type checking". Of course as is much safer than an unsafe TSomething(X) cast. Commented Mar 25, 2021 at 10:46
4

One option might be to declare a public interface that TB implements, and then TA can have a field of that interface type.

interface
    
type
  IB = interface
    //something
  end;

  TA = class
  public
    B : IB;
    constructor Create;
  end;
    
//something
    
implementation
    
type
  TB = class(TInterfacedObject, IB)
    //something
  end;

constructor TA.Create;
begin
  B := TB.Create;
end;
1
  • I think that the OP wants even the IB interface to be hidden from other units, but I agree it is not clear exactly what the OP is trying to do. Commented Mar 24, 2021 at 23:04
3

Is it possible to use a class declared in Implementation section from Interface section?

No.

Or is there a way to declare TB in the Interface section but make it kinda private?

Yes,, if you make it a nested class, declare in a private section of the containing type.

2

A pointer and class can be declared forward, but must be declared in the same type block. The reason that this works is because they are reference types and even just the forward declaration fixates the offset of the fields after them. The type block bit is mainly compiler writer convenience (but as so often, also easier/better error message generation)

Pascal follow-up Modula2 allowed pointers to be more narrowly defined in the implementation, e.g. a pointer to a certain record(so called opaque types). Other units could only use it as a handle type (pass it along etc) and the implementation could access details without typecasts. In that way it is a language assisted way of doing what Fpiette suggests, tobject being the most basic subset of a class.

Another solution would be to make it a generic, and specialize it in the implementation with the generic TB type.

Not the answer you're looking for? Browse other questions tagged or ask your own question.