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?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Bruice
  • 543
  • 3
  • 11
  • Why does `TA` need a field that refers to a type that hasn't been defined yet? What is the goal here? – Remy Lebeau Mar 24 '21 at 22:51
  • Do you have a use-case in mind for this? – MartynA Mar 25 '21 at 09: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 Mar 25 '21 at 15:14

5 Answers5

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.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
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.
fpiette
  • 11,983
  • 1
  • 24
  • 46
  • 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. – Andreas Rejbrand Mar 25 '21 at 08: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 Mar 25 '21 at 09:28
  • Sorry, I mean "compile-time type checking". Of course `as` is much safer than an unsafe `TSomething(X)` cast. – Andreas Rejbrand Mar 25 '21 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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 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. – Andreas Rejbrand Mar 24 '21 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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
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.

Marco van de Voort
  • 25,628
  • 5
  • 56
  • 89