* * *

Author Topic: Circular reference  (Read 7768 times)

Bill52

  • New member
  • *
  • Posts: 8
Circular reference
« on: September 01, 2014, 02:19:35 pm »
I have two classes: ClassA and ClassB.
ClassA stores a list of pointers to ClassB.
ClassB stores a pointer to ClassA.

Both classes have public functions/procedures to set/get the pointers. IE: can't move the 'uses' clause to the implementation.
The classes have nothing in common.  It is a 'has-a' not an 'is-a' relation. IE: inheritance doesn't apply.

How do i go about resolving the circular reference?

Thanks,
bill


Blaazen

  • Hero Member
  • *****
  • Posts: 2370
  • POKE 54296,15
    • Eye-Candy Controls
Re: Circular reference
« Reply #1 on: September 01, 2014, 02:30:18 pm »
There exists "forward declaration":
Code: [Select]
ClassB = class;

ClassA = class
...//references to ClassB
end;

ClassB = class
...//references to ClassA
end;

But, do I understand well, your classes are in different units?

EDIT: I don't see other way than moving classes to the same unit.
« Last Edit: September 01, 2014, 02:36:23 pm by Blaazen »
Lazarus 1.9 r54600M FPC 3.0.2 x86_64-linux-qt Chakra, Qt 4.8.7, Plasma 5.9.4
Lazarus 1.6.4 r54278 FPC 3.0.2 i386-win32-win32/win64 Wine 2.4










Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

Bill52

  • New member
  • *
  • Posts: 8
Re: Circular reference
« Reply #2 on: September 01, 2014, 02:48:33 pm »
Yes, they are in separate units. Each are some 150 lines.
Moving them to one unit would be awful.

Blaazen

  • Hero Member
  • *****
  • Posts: 2370
  • POKE 54296,15
    • Eye-Candy Controls
Re: Circular reference
« Reply #3 on: September 01, 2014, 03:00:08 pm »
You can use references to - for example - TObject (in declaration) and use casting in implementation, i.e.:
Code: [Select]
with ReferenceToTObject as ClassB do
  begin

  end;
which will allow you to move one of the units to the 'uses' clause to the implementation.

But it seems to me much more work than just CTRL+C and CTRL+V the classes to the same unit.
Lazarus 1.9 r54600M FPC 3.0.2 x86_64-linux-qt Chakra, Qt 4.8.7, Plasma 5.9.4
Lazarus 1.6.4 r54278 FPC 3.0.2 i386-win32-win32/win64 Wine 2.4










Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

taazz

  • Hero Member
  • *****
  • Posts: 3747
Re: Circular reference
« Reply #4 on: September 01, 2014, 03:37:32 pm »
Well 150 lines of code is not that big and there is nothing awful of having all the related classes in one unit it is called encapsulation. Other than what blaazen told you, you can have a 3rd unit where the abstract classes of those two are defined and that unit is used from the class implementation units.

Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 5340
Re: Circular reference
« Reply #5 on: September 01, 2014, 05:02:42 pm »
Or separate one class into a minimal base class (what the other needs) and derive it for the rest of the implementation.

Code: [Select]
unita:

type
     classb= class;
      baseclassa = class
        // references classb
        end
       
        classb = class
                // references baseclassa
            end;
                       
unitb:
     classa=class(baseclassa)
               // rest of classa
           end;

circular

  • Hero Member
  • *****
  • Posts: 2567
    • Personal webpage
Re: Circular reference
« Reply #6 on: September 01, 2014, 08:26:03 pm »
Talking about me? :D

All suggestions seem fine:
- using TObject for the parameters of the functions
- using a base class for A and/or for B
- merging into one unit
Conscience is the debugger of the mind

hinst

  • Sr. Member
  • ****
  • Posts: 303
Re: Circular reference
« Reply #7 on: September 01, 2014, 10:18:01 pm »
moving unit dependency to implementation section of the unit and then using Pointer or TObject type for argument is a bad idea in my opinion.

First, TObject type does not say much about type of argument, so you will have to deduce what kind of argument is expected every time you insert a call to that method somewhere or remember it so you should have a good memory or you can put a comment like // pass only TSpecificType here
Well, it's unsafe

Second reason not to do this, and probably more important: FreePascal compiler sometimes has problems with units referencing each other, so it might start throwing runtime errors. It happened to me. I had project containing about 20 units in total, and some of them referenced each other in implementation forming some kind of circle. At some point FPC refused to compile it. Not sure if it was fixed later. I decided to eliminate circular references completely at that time, however there was another option: I noticed that if I clear build files each time, it could still compile my project, but I decided not to rely on it since it still could stop working completely at some point and also if you always clear compiled files they take more time to compile; well you get the idea
Too late to escape fate

skalogryz

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1800
Re: Circular reference
« Reply #8 on: September 02, 2014, 03:33:39 am »
moving unit dependency to implementation section of the unit and then using Pointer or TObject type for argument is a bad idea in my opinion.

First, TObject type does not say much about type of argument, so you will have to deduce what kind of argument is expected every time you insert a call to that method somewhere or remember it so you should have a good memory or you can put a comment like // pass only TSpecificType here
Well, it's unsafe
using Pointer is unsafe.
using TObject is safe and it says enough about itself.
Code: [Select]
procedure SomeCallBack( obj: TObject);
var
  sp : TSpecificType;
begin
  if not (obj is TSpecificType) then Exit;
  sp:=TSpecificType(obj);
  ...
end;

Leledumbo

  • Hero Member
  • *****
  • Posts: 7556
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Circular reference
« Reply #9 on: September 02, 2014, 10:59:17 am »
150 lines are short (anybody wants to open packages/univint/Movies.pas?). The use of units are to group things of the same realm, so it's correct to group related classes into one unit. If you think it's cumbersome to maintain due to long lines, use include files. The compiler and IDE has full support for include files, classes from different include files will still be part of the same unit.

taazz

  • Hero Member
  • *****
  • Posts: 3747
Re: Circular reference
« Reply #10 on: September 02, 2014, 11:36:28 am »
If you think it's cumbersome to maintain due to long lines, use include files.

The problem with the include files (and my main obstacle to start using C) is that for each component you need to create 2 include files one for interface and one for implementation splitting the code in to more files than using two units, it is a PITA.

I think that include files is correctly used in delphi where they are used for minor support issue although lazarus has inbuild support for inc file and can auto jump to the correct file I wouldn't call that full support just abuse.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

howardpc

  • Hero Member
  • *****
  • Posts: 2119
Re: Circular reference
« Reply #11 on: September 02, 2014, 11:50:36 am »
The problem with the include files (and my main obstacle to start using C) is that for each component you need to create 2 include files one for interface and one for implementation splitting the code in to more files than using two units, it is a PITA.

An oft-seen LCL convention is to keep all related class declarations together in the interface of the "main group" unit, and then have an implementation section that lists {$I xxx.inc} statements corresponding to each of the class declarations from the interface above. This means you (almost) have a one-to-one class/disk-file relationship when it comes to maintenance. It seems a good compromise.
« Last Edit: September 02, 2014, 11:52:40 am by howardpc »

taazz

  • Hero Member
  • *****
  • Posts: 3747
Re: Circular reference
« Reply #12 on: September 02, 2014, 11:54:38 am »
The problem with the include files (and my main obstacle to start using C) is that for each component you need to create 2 include files one for interface and one for implementation splitting the code in to more files than using two units, it is a PITA.
An oft-seen LCL convention is to keep all related class declarations together in the interface of the "main group" unit, and then have an implementation section that lists {$I xxx.inc} statements corresponding to each of the class declarations from the interface above. This means you (almost) have a one-to-one class/disk-file relationship when it comes to maintenance.

I know and for that reason I hate delving in the lcl code and avoid if I can. One of pascal's strengths is encapsulation(ee all relevant code in one unit) include files as used in lcl brakes this convention and I always end up using some kind of grep to find what I'm looking for instead of the editor.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Edson

  • Hero Member
  • *****
  • Posts: 714
Re: Circular reference
« Reply #13 on: September 02, 2014, 05:50:15 pm »
I think using include files, is the best option, when having related classes on the interface section and  a lot of code for implementation.
What I do is to try to separate the part of code, that is not going to change frequently, in the included file. This leave me the a small code and easier to maintain.
I know, this break the encapsulation in some way, but it avoid to have many thousands of code on one single file.
Lazarus 1.6 - FPC 3.0.0 - x86_64-win64 on  Windows 7

Leledumbo

  • Hero Member
  • *****
  • Posts: 7556
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Circular reference
« Reply #14 on: September 02, 2014, 06:43:16 pm »
The problem with the include files (and my main obstacle to start using C) is that for each component you need to create 2 include files one for interface and one for implementation splitting the code in to more files than using two units, it is a PITA.
Look at windows related API implementation in the rtl. they only need one include file ;)
If you can't find, this is the way to do it:
Code: [Select]
// file.inc
{$ifdef read_interface}

here goes interface code

{$else read_interface}

here goes implementation code

{$endif read_interface}
then:
Code: [Select]
unit x;

interface

{$define read_interface}
{$i file.inc}

implementation

{$undef read_interface}
{$i file.inc}

end.

Include files do NOT break encapsulation in anyway, if we're talking about the same term. Unless you have your own definition, you have to be clear about that first.
« Last Edit: September 02, 2014, 06:44:53 pm by Leledumbo »

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus