Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2005-03

"Friend" classes?

23 messages · 8 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

DrDiettrich

I just came about code that uses protected members of other classes, defined in other units. In Delphi this possible by a declaration like:

type TFriendClass = class(TNotMyClass);

After this declaration the protected (and private?) members of TNotMyClass are accessible, using TFriendClass as an type alias.

Now my questions:

1) Is such behaviour also available with FPC? In non-Delphi modes?

2) How portable are such "friend" classes? Does there exist some (other) standard for this purpose?

3) What alternatives could be used, so that not all class members must be made public, when they shall be used only in specific related units?

DoDi

Reply by email

Michael Van Canneyt
to DrDiettrich
DrDiettrich wrote
I just came about code that uses protected members of other classes, defined in other units. In Delphi this possible by a declaration like: type TFriendClass = class(TNotMyClass);

This is a simple descendent.

Show quoted text 7 lines
After this declaration the protected (and private?) members of TNotMyClass are accessible, using TFriendClass as an type alias. Now my questions: 1) Is such behaviour also available with FPC? In non-Delphi modes?

Yes. Only protected.

2) How portable are such "friend" classes? Does there exist some (other) standard for this purpose?

The above is a simple descendent, so I'm not sure about this 'friend' ?

3) What alternatives could be used, so that not all class members must be made public, when they shall be used only in specific related units?

? Please explain.

Michael.

Reply by email

DrDiettrich
to Michael Van Canneyt
Michael Van Canneyt wrote
> type TFriendClass = class(TNotMyClass); This is a simple descendent.

Yes and no. The only purpose of this declaration is to get access to the protected members of the class, not to extend the class in any way.

> 3) What alternatives could be used, so that not all class members must > be made public, when they shall be used only in specific related units? ? Please explain.

"protected" members of a class are for internal use only, not for public use. IMO it's against the spirit of visibility levels, when the protected and even private members of a class become accessible from outside the class, throughout the whole unit in which a class is defined. But sometimes multiple classes are designed for cooperation, where one class needs access to not normally accessible methods or properties of another class. When now the required methods are made public, they become available for abuse by everybody. With the above trick instead it's possible to gain access to the internals of a class, without making everything public. Such a backdoor does not prevent abuse, but as the (C++) term "friend" class suggests, it exists only for the sake of cooperation, and at the responsibility of the implementor.

That's why I would like to know how such cooperative functionality can be implemented, by perhaps less tricky means.

DoDi

Reply by email

Michael Van Canneyt
to DrDiettrich
DrDiettrich wrote 8 lines
Michael Van Canneyt wrote: >> type TFriendClass = class(TNotMyClass); > > This is a simple descendent. Yes and no. The only purpose of this declaration is to get access to the protected members of the class, not to extend the class in any way.

Yes. This is OK, no ?

Show quoted text 22 lines
>> 3) What alternatives could be used, so that not all class members must >> be made public, when they shall be used only in specific related units? > > ? Please explain. "protected" members of a class are for internal use only, not for public use. IMO it's against the spirit of visibility levels, when the protected and even private members of a class become accessible from outside the class, throughout the whole unit in which a class is defined. But sometimes multiple classes are designed for cooperation, where one class needs access to not normally accessible methods or properties of another class. When now the required methods are made public, they become available for abuse by everybody. With the above trick instead it's possible to gain access to the internals of a class, without making everything public. Such a backdoor does not prevent abuse, but as the (C++) term "friend" class suggests, it exists only for the sake of cooperation, and at the responsibility of the implementor. That's why I would like to know how such cooperative functionality can be implemented, by perhaps less tricky means.

sorry, but I fail to see the problem ? The above makes all protected members of a class visible. This is normal, that is why they are protected. If you want to avoid that, make the members private. Then they are visible only in the same unit, even for other classes (for 'cooperation'), but not outside the unit, even with a descendent.

Michael.

Reply by email

Ales Katona
to Michael Van Canneyt

Michael Van Canneyt wrote / napísal (a):

Show quoted text 57 lines
On Wed, 16 Mar 2005, DrDiettrich wrote: > Michael Van Canneyt wrote: > >>> type TFriendClass = class(TNotMyClass); >> >> >> This is a simple descendent. > > > Yes and no. The only purpose of this declaration is to get access to the > protected members of the class, not to extend the class in any way. Yes. This is OK, no ? > >>> 3) What alternatives could be used, so that not all class members must >>> be made public, when they shall be used only in specific related >>> units? >> >> >> ? Please explain. > > > "protected" members of a class are for internal use only, not for public > use. IMO it's against the spirit of visibility levels, when the > protected and even private members of a class become accessible from > outside the class, throughout the whole unit in which a class is > defined. But sometimes multiple classes are designed for cooperation, > where one class needs access to not normally accessible methods or > properties of another class. When now the required methods are made > public, they become available for abuse by everybody. With the above > trick instead it's possible to gain access to the internals of a class, > without making everything public. > Such a backdoor does not prevent > abuse, but as the (C++) term "friend" class suggests, it exists only for > the sake of cooperation, and at the responsibility of the implementor. > > That's why I would like to know how such cooperative functionality can > be implemented, by perhaps less tricky means. sorry, but I fail to see the problem ? The above makes all protected members of a class visible. This is normal, that is why they are protected. If you want to avoid that, make the members private. Then they are visible only in the same unit, even for other classes (for 'cooperation'), but not outside the unit, even with a descendent. Michael. _______________________________________________ fpc-devel maillist - http://lists.freepascal.org/mailman/listinfo/fpc-devel

C++ requires "friend" only because it lacks the idea of modularity. Since all classes are "apart" they need some way to tell each other "I can use you"
In pascal you simply put them into 1 unit.

Ales

Reply by email

DrDiettrich
to Michael Van Canneyt: Yes. This is OK, no ? sorry, but I fail to see the problem ? The above makes all…
Michael Van Canneyt wrote 5 lines
sorry, but I fail to see the problem ? The above makes all protected members of a class visible. This is normal, that is why they are protected. If you want to avoid that, make the members private. Then they are visible only in the same unit, even for other classes (for 'cooperation'), but not outside the unit, even with a descendent.

I.e. "protected" means to you that there exist means to bypass that protection?

Hmm, I think that I can update my mental class model accordingly ;-)

Thanks
DoDi

Reply by email

DrDiettrich
to Ales Katona: Michael Van Canneyt wrote / napísal (a): C++ requires "friend" only because it lacks…
Ales Katona wrote
C++ requires "friend" only because it lacks the idea of modularity. Since all classes are "apart" they need some way to tell each other "I can use you" In pascal you simply put them into 1 unit.

That's why the C++ model is better, there exists no requirement to implement related classes in a single module.

In porting C++ code to Pascal I often stumbled into circular unit references. Then the only solution is a monster unit, that implements all the related classes at once, where the C++ implementation can be split into appropriate modules. Even in Java it's possible to implement every class in a dedicated unit, regardless of class relationships, and without a need for separate header files. That's what I call a good modular concept.

Perhaps I dislike Pascal include files only because they are poorly supported by the Delphi IDE. With better support it were possible to split the implementation into multiple dedicated include files, which could be thought of as implementation files, according to e.g. the Modula model. Lazarus offers better support for included files, but unfortunately it currently groups the types, constants etc. overview together by the according clauses; I hope for better grouping options in the near future, so that e.g. all types of an unit can reside in a single group. I already considered to contribute to the Lazarus development, but currently I have other projects with higher priority...

DoDi

Reply by email

Micha Nelissen
to DrDiettrich
In porting C++ code to Pascal I often stumbled into circular unit references. Then the only solution is a monster unit, that implements all the related classes at once, where the C++ implementation can be split into appropriate modules.

The real question is: was the design of the code ok ? Circular references makes code harder to understand. Layers are easier to understand.

Micha

Reply by email

Florian Klaempfl
to Micha Nelissen
Micha Nelissen wrote 12 lines
On Sat, 19 Mar 2005 10:04:55 +0100 DrDiettrich wrote: >In porting C++ code to Pascal I often stumbled into circular unit >references. Then the only solution is a monster unit, that implements >all the related classes at once, where the C++ implementation can be >split into appropriate modules. The real question is: was the design of the code ok ? Circular references makes code harder to understand. Layers are easier to understand.

C++ creates one monster module in this case as well. You could do the same as in C++: split everything into include files. Pascal units aren't 1:1 equivalent of C++ include files, if C++ libraries are done properly, you can translate namespaces to pascal units.

Reply by email

Peter J. Haas
to Ales Katona: Michael Van Canneyt wrote / napísal (a): C++ requires "friend" only because it lacks…

Hi Ales,

Ales wrote
C++ requires "friend" only because it lacks the idea of modularity. Since all classes are "apart" they need some way to tell each other "I can use you" In pascal you simply put them into 1 unit.

But IMCO this is not really a good OOP-like solution. Above all because every class in the unit have access to all sections of a other class, including the private section (at least in Delphi). A real private section is on my wish list since Delphi 1.

Sometimes is it not possible or meaninful to put all related classes in 1 unit, e.g. to avoid monster units and split a problem in surveyable parts, although this classes belong together and are managed by a furthermore class. Then you need to publish more methods, as you need for other resp. the actual usage, this softed the information hiding concept.

Years ago I read a description of a object oriented pascal dialect, which use a other concept, IIRC called view concept. The developer can declare different views to a class, which only contain the methods, which are needed for a concrete problem. I think such a concept would be the better way.

The developer of a class or component don't need to consider, which methods / fields he need to set in the protected part. A developer which derive a class can define, which methods / fields he need and write a view without any hacks or modification of the origin class.

wkr Peter.

Reply by email

Ales Katona
to DrDiettrich: That's why the C++ model is better, there exists no requirement to implement related…

DrDiettrich wrote / napísal (a):

Show quoted text 43 lines
Ales Katona wrote: >C++ requires "friend" only because it lacks the idea of modularity. >Since all classes are "apart" they need some way to tell each other "I >can use you" >In pascal you simply put them into 1 unit. > > That's why the C++ model is better, there exists no requirement to implement related classes in a single module. In porting C++ code to Pascal I often stumbled into circular unit references. Then the only solution is a monster unit, that implements all the related classes at once, where the C++ implementation can be split into appropriate modules. Even in Java it's possible to implement every class in a dedicated unit, regardless of class relationships, and without a need for separate header files. That's what I call a good modular concept. Perhaps I dislike Pascal include files only because they are poorly supported by the Delphi IDE. With better support it were possible to split the implementation into multiple dedicated include files, which could be thought of as implementation files, according to e.g. the Modula model. Lazarus offers better support for included files, but unfortunately it currently groups the types, constants etc. overview together by the according clauses; I hope for better grouping options in the near future, so that e.g. all types of an unit can reside in a single group. I already considered to contribute to the Lazarus development, but currently I have other projects with higher priority... DoDi _______________________________________________ fpc-devel maillist - http://lists.freepascal.org/mailman/listinfo/fpc-devel

This is actualy a C problem. If you imagine the outcome any bigger C program is one big piece of code. If C/C++ had proper modularity support, things like this wouldn't happen.

Ales

Reply by email

peter green
to DrDiettrich: Yes and no. The only purpose of this declaration is to get access to the protected…
Show quoted text 16 lines
-----Original Message----- From: Behalf Of DrDiettrich Sent: 16 March 2005 10:59 To: FPC developers' list Subject: Re: [fpc-devel] "Friend" classes? Michael Van Canneyt wrote: > > type TFriendClass = class(TNotMyClass); > > This is a simple descendent. Yes and no. The only purpose of this declaration is to get access to the protected members of the class, not to extend the class in any way.

note: protected in object pascal isn't really very well protected

if you need to get at a protected method/field/property of a class you can always do so by creating a descendent and using an unchecked typecast.

Reply by email

DrDiettrich
to Micha Nelissen: On Sat, 19 Mar 2005 10:04:55 +0100 The real question is: was the design of the code ok ?…
Micha Nelissen wrote
The real question is: was the design of the code ok ?

Some dependencies cannot be removed by a redesign :-(

In some cases interfaces instead of classes can help, because they don't need implementation in the same unit. But then the classes have to be implemented as well.

IMO it's important to keep classes encapsulated in distinct units, so that no unintended interaction (use of private/protected members of other classes...) can occur, as is possible in a single unit.

Circular references makes code harder to understand. Layers are easier to understand.

I see no problem in understanding here? When two objects refer to each other, but the objects are not otherwise functionally related, they can be understood without knowing about the other one. It also should be possible to implement all these objects seaparately.

DoDi

Reply by email

DrDiettrich
to Florian Klaempfl: C++ creates one monster module in this case as well. You could do the same as in C++:…
Florian Klaempfl wrote
C++ creates one monster module in this case as well.

I disagree. Neither the declarations (interface, header files) nor the definitions (implementation, code modules) must reside in one file. C header files can #include each other, and some more or less advanced techniques can prevent multiple inclusion of the same header file.

You could do the same as in C++: split everything into include files. Pascal units aren't 1:1 equivalent of C++ include files, if C++ libraries are done properly, you can translate namespaces to pascal units.

Just a question about the compilation in C/C++ and Pascal. A C compiler collects all required header files, prior to compiling a source module. The recompilation of the header files for every single code file is one reason for the slow C/C++ compilers. Namespaces IMO only have been introduced to reduce the size of the symbol tables, resulting from the many required header files.

In modern Pascal, as I see it, the compilation of every unit is done in multiple steps, or passes, not in one pass as was possible with the single-unit original Pascal. In the first pass the Interface sections are compiled, of the unit itself and of all units in the Interface Uses list. The resulting compiled Interface section now can be used in the compilation of every other unit. In the second step the Implementation part of the unit is compiled, including the Implementation Uses list.

Or how else can the Interface section of unit A use unit B, when the Implementation section of unit B uses unit A?

Sure, compiler threads for every unit are a quasi-parallel solution, but then the threads become blocked between the compilation of the interface and implementation sections, effectively splitting the compilation process into 2 distinct steps.

DoDi

Reply by email

Florian Klaempfl
to DrDiettrich
DrDiettrich wrote 8 lines
Florian Klaempfl wrote: >C++ creates one monster module in this case as well. I disagree. Neither the declarations (interface, header files) nor the definitions (implementation, code modules) must reside in one file.

How the sources are splitted doesn't matter. The compiler handles it as one file and creates one object module. The compiler even doesn't see that the files are splitted, the preprocessor concats everything.

C header files can #include each other, and some more or less advanced techniques can prevent multiple inclusion of the same header file.

Pascal can do the same, see the fpc rtl: for easier handling they are splitted into multiple files.

Units are a higher level concept than the include files of C++ but units can often but not necessarily reflect the include file structure.

If you want 1:1 translations use include files in pascal as well.

Reply by email

DrDiettrich
to Florian Klaempfl
Florian Klaempfl wrote 9 lines
>>C++ creates one monster module in this case as well. > > > I disagree. Neither the declarations (interface, header files) nor the > definitions (implementation, code modules) must reside in one file. How the sources are splitted doesn't matter. The compiler handles it as one file and creates one object module. The compiler even doesn't see that the files are splitted, the preprocessor concats everything.

Please let me quote the originial statement:

>In porting C++ code to Pascal I often stumbled into circular unit >references. Then the only solution is a monster unit, that implements >all the related classes at once, where the C++ implementation can be >split into appropriate modules.

In one case I had about 10 C++ modules, which implemented one class each. These modules can be compiled separately, into the same number of object files.

In Pascal I had to merge all these modules into an single unit, due to circular unit references. This monster unit then compiles into one object file, of course.

Perhaps you missed that in C/C++ the preprocessor is typically (99.999%) used to include header files, not source files. This is comparable to Pascal "uses", not to {$Include}!

Units are a higher level concept than the include files of C++ but units can often but not necessarily reflect the include file structure.

There exist many kinds of concepts, with regards to the relationship between declarations (interfaces) and definitions (implementations). In C/C++ both are separated and strictly unrelated, in Modula both are separated into strictly related files, in Pascal or Java both are combined into single unit (class) files. I cannot see any "levels" in these concepts. Adding namespaces results in a 3D model, of declarations, namespaces, and implementations.

For the compound Pascal units, the most important *difference* is the required implementation of everything, what's declared in the interface section of an unit, in the implementation section of the *same* unit. This requirement leaves no room for inconsistent sets of interfaces (header files) and implementations (source modules).

The only *disadvantage* of units are the current Pascal compilers, which cannot handle circular unit references :-(

DoDi

Reply by email

Micha Nelissen
to DrDiettrich
Perhaps you missed that in C/C++ the preprocessor is typically (99.999%) used to include header files, not source files. This is comparable to Pascal "uses", not to {$Include}!

What's the difference between a 'header' file, and a source file ? Header files often contain template classes with inline implemented methods.

Preprocessor is used also for macros.

Please look again at 'uses', it works on a more abstract level than just recompiling all units depended on, it's where the speed of compiling pascal comes from.

Show quoted text 5 lines
For the compound Pascal units, the most important *difference* is the required implementation of everything, what's declared in the interface section of an unit, in the implementation section of the *same* unit. This requirement leaves no room for inconsistent sets of interfaces (header files) and implementations (source modules).

An advantage, I would say.

The only *disadvantage* of units are the current Pascal compilers, which cannot handle circular unit references :-(

No, it's an advantage: it makes the code and design clearer, plus it increases the speed of compilation a *lot*.

Micha

Reply by email

DrDiettrich
to Micha Nelissen
Micha Nelissen wrote 6 lines
> Perhaps you missed that in C/C++ the preprocessor is typically (99.999%) > used to include header files, not source files. This is comparable to > Pascal "uses", not to {$Include}! What's the difference between a 'header' file, and a source file ? Header > files often contain template classes with inline implemented methods. Preprocessor is used also for macros.

The definitions of templates, inline procedures or macros do not immediately contribute to the size of a compiled module, only when they are *used* in code modules.

Please look again at 'uses', it works on a more abstract level than just recompiling all units depended on, it's where the speed of compiling pascal comes from.

In comparison with C/C++, "uses" summarizes the #include of header files and the dependency checks of Make. But it's not only found in Pascal, e.g. Java has "imports" for almost the same purpose. The original Wirth Pascal had no "uses", it was added later.

Many properties make Pascal compilers faster than C/C++ compilers. The effect of "uses" is equivalent to C/C++ precompiled header files.

Show quoted text 5 lines
> The only *disadvantage* of units are the current Pascal compilers, which > cannot handle circular unit references :-( No, it's an advantage: it makes the code and design clearer, plus it increases the speed of compilation a *lot*.

I wouldn't call a design clearer when it requires to implement most of a project in a single unit :-(

I also doubt about the speed increase, as long as nobody tried to write an Pascal compiler that can handle circular unit references.

DoDi

Reply by email

peter green
to Micha Nelissen: On Thu, 24 Mar 2005 09:02:30 +0100 What's the difference between a 'header' file, and a…
Show quoted text 6 lines
> The only *disadvantage* of units are the current Pascal compilers, which > cannot handle circular unit references :-( No, it's an advantage: it makes the code and design clearer, plus it increases the speed of compilation a *lot*.

note: you can have cuircular implementation references just not cuircular interface references.

so sometimes some extra typecasts will be needed if classes need to have referenes to each other but its not usually any worse than that.

converting between OOP languages will always be hard because they all use different object models and most code is designed around the object model of the language in use.

Reply by email

Marco van de Voort
to DrDiettrich: The definitions of templates, inline procedures or macros do not immediately contribute…
Show quoted text 12 lines
Micha Nelissen wrote: > > Perhaps you missed that in C/C++ the preprocessor is typically (99.999%) > > used to include header files, not source files. This is comparable to > > Pascal "uses", not to {$Include}! > > What's the difference between a 'header' file, and a source file ? Header > files often contain template classes with inline implemented methods. > Preprocessor is used also for macros. The definitions of templates, inline procedures or macros do not immediately contribute to the size of a compiled module, only when they are *used* in code modules.

That goes for all routines.

Show quoted text 6 lines
> Please look again at 'uses', it works on a more abstract level than just > recompiling all units depended on, it's where the speed of compiling pascal > comes from. In comparison with C/C++, "uses" summarizes the #include of header files and the dependency checks of Make.

The important difference (in TP/Delphi/FPC) is that preprocessor state doesn't follow USES statements.

That allows the compiler to auto-find the compilation order..

Many properties make Pascal compilers faster than C/C++ compilers. The effect of "uses" is equivalent to C/C++ precompiled header files.

The effect of units is that it is a lot safer (more guaranteed) and easier to implement that precompiled header files, and auto-building is also a lot easier (not requiring explicit manual compile order enforced)

> > The only *disadvantage* of units are the current Pascal compilers, which > > cannot handle circular unit references :-(

It could in theory deal with some forms of circular records. Specially in the case of e.g. classes.

Just recompile till CRCs don't change anymore. This allows units that circularly import eachother, but have no real circular dependancy of types to compile. Or even if the circular reference only involves types of which the sizes are known (like class etc types that are only 4 bytes).

However it is of course questionable if it is worth the trouble to implement this, and make it bullet proof. Maybe somebody who does graph theory as a hobby?

Show quoted text 5 lines
> No, it's an advantage: it makes the code and design clearer, plus it > increases the speed of compilation a *lot*. I wouldn't call a design clearer when it requires to implement most of a project in a single unit :-(

Why would you have to ?

I also doubt about the speed increase, as long as nobody tried to write an Pascal compiler that can handle circular unit references.

I fail to see the connection between what the speed increase has to do with not handling circular reference systems?

IIRC TP could handle mild circular references and so can FPC. I don't know if this is still the case.

Reply by email

DrDiettrich
to Marco van de Voort
Marco van de Voort wrote 5 lines
> The definitions of templates, inline procedures or macros do not > immediately contribute to the size of a compiled module, only when they > are *used* in code modules. That goes for all routines.

I'm not sure what you mean. A global procedure, exported in the interface section of a unit, always must be be compiled. Only the linker can determine which procedures really are used in a project, and which ones can be omitted. A specific procedure can be used in one project, but can be unused in another project. Think of the standard units like System...

Show quoted text 5 lines
> In comparison with C/C++, "uses" summarizes the #include of header files > and the dependency checks of Make. The important difference (in TP/Delphi/FPC) is that preprocessor state doesn't follow USES statements.

Do you mean that only one level of dependencies must be checked with "uses", whereas even the indirectly #included files must be checked for changes?

That allows the compiler to auto-find the compilation order..

What's the importance of a compilation order?

Show quoted text 6 lines
> Many properties make Pascal compilers faster than C/C++ compilers. The > effect of "uses" is equivalent to C/C++ precompiled header files. The effect of units is that it is a lot safer (more guaranteed) and easier to implement that precompiled header files, and auto-building is also a lot easier (not requiring explicit manual compile order enforced)

Again I don't understand what you mean (compile order???) :-(

A C project doesn't require any compilation order, every module can be compiled independently from other modules. The problem is not the order, instead it's the condition, *when* a module has to be recompiled.

Show quoted text 5 lines
> > > The only *disadvantage* of units are the current Pascal compilers, which > > > cannot handle circular unit references :-( It could in theory deal with some forms of circular records. Specially in the case of e.g. classes.

I can imagine some kind of extended "forward" declarations, extending into other units. In most cases it's suffient, for compilation, that the general kind of a named type is known, in detail with pointers and references. Then it's possible to layout records, classes or other structured data types, including parameter lists of subroutines, without the exact knowledge of the referenced types. Knowledge about the exact types usually is required only when members of some referenced complex type come into the play, what does not normally happen in declarations (interface part of units).

Perhaps I should give some examples:

type
  A = record a1: B; end;
  B = record b1: A; end;

Such a construct of course is illegal, because of infinite recursion. The same construct would be perfectly accepatble with pointers:

type
  A = record a1: ^B; end;
  B = record b1: ^A; end;

My idea for extended external declarations:

type
  C = class in OtherUnit;
or
  OtherUnit.C = class;

Now the compiler can insert a reference to OtherUnit.C for every occurence of C in the remainder of the interface section. The OtherUnit now can be moved from the uses list of the interface section into the uses list of the implementation section; or it's imported implicitly, in the most comfortable case.

Just recompile till CRCs don't change anymore. This allows units that circularly import eachother, but have no real circular dependancy of types to compile. Or even if the circular reference only involves types of which the sizes are known (like class etc types that are only 4 bytes).

I would be happy with the latter case and no recompilation at all.

However it is of course questionable if it is worth the trouble to implement this, and make it bullet proof. Maybe somebody who does graph theory as a hobby?

I don't qualify as graph theory guru, even if I have already implemented the analysis of control flow graphs in my decompilers. In the case of circular unit references I'd ignore the unit dependencies for themselves, and instead concentrate on the type definitions themselves. There I'd use a list of type names, with some fixed attributes (unit, size, immediate type...), and a reference to the detailed definition of the base type, in case of more complex types. Then the existence of a (illegal) loop can be determined from a Nil reference, whenever access to the detailed definition is actually required. The unit attribute in the fixed part of a type definition is required to distinguish definitions of the same name in different units, and it also can be used to complete forward (or external) type references when the defining unit is imported later.

IIRC TP could handle mild circular references and so can FPC. I don't know if this is still the case.

Sounds interesting, do you have more information?

DoDi

Reply by email

Marco van de Voort
to DrDiettrich
Show quoted text 14 lines
Marco van de Voort wrote: > > immediately contribute to the size of a compiled module, only when they > > are *used* in code modules. > > That goes for all routines. I'm not sure what you mean. A global procedure, exported in the interface section of a unit, always must be be compiled. Only the linker can determine which procedures really are used in a project, and which ones can be omitted. A specific procedure can be used in one project, but can be unused in another project. Think of the standard units like System...

Yeah, but smartlinking/deadcodeelimination is a pretty standard feature.

Show quoted text 9 lines
> > In comparison with C/C++, "uses" summarizes the #include of header files > > and the dependency checks of Make. > > The important difference (in TP/Delphi/FPC) is that preprocessor state > doesn't follow USES statements. Do you mean that only one level of dependencies must be checked with "uses", whereas even the indirectly #included files must be checked for changes?

You always have to do the #include. Always. Pre-compiled headers are possible, not trivial, since it requires a comparison of the entire preprocessor state to see if a header needs recompilation.

> That allows the compiler to auto-find the compilation order.. What's the importance of a compilation order?

It is something that doesn't come cheap.

Show quoted text 5 lines
> The effect of units is that it is a lot safer (more guaranteed) and easier > to implement that precompiled header files, and auto-building is also a lot > easier (not requiring explicit manual compile order enforced) Again I don't understand what you mean (compile order???) :-(

Not recompiling the headers each time saves a lot of time.

A C project doesn't require any compilation order, every module can be compiled independently from other modules.

Implementation: yes. Header: no.

[snip]

Show quoted text 6 lines
Such a construct of course is illegal, because of infinite recursion. The same construct would be perfectly accepatble with pointers: type A = record a1: ^B; end; B = record b1: ^A; end;

Yes. But it doesn't even have to so complex. Simply two files having dependancies on eachother, without having cycles in types are most typical.

My idea for extended external declarations:

IMHO it is not a problem that needs solving, specially not with extra syntax.

> IIRC TP could handle mild circular references and so can FPC. I don't know > if this is still the case. Sounds interesting, do you have more information?

No. It could also be that one of the other devels remember how it exactly went.

I quickly tested, and it doesn't work now:

-bash-2.05b$ fpc unit1
Free Pascal Compiler version 1.9.9 [2005/03/29] for i386 Copyright (c) 1993-2005 by Florian Klaempfl
Target OS: FreeBSD/ELF for i386
Compiling unit1.pas
Compiling unit2.pas
unit2.pas(5,11) Fatal: Circular unit reference between unit2 and unit1 Error: /home/marcov/bin/ppc386 returned an error exitcode (normal if you did not specifiy a source file to be compiled)

So if it worked at a certain point it was removed later.

Reply by email

DrDiettrich
to Marco van de Voort
Marco van de Voort wrote 7 lines
> Do you mean that only one level of dependencies must be checked with > "uses", whereas even the indirectly #included files must be checked for > changes? You always have to do the #include. Always. Pre-compiled headers are possible, not trivial, since it requires a comparison of the entire preprocessor state to see if a header needs recompilation.

With precompiled headers it's almost like with compiled units: every compiled file contains a list of dependend files, dates and/or checksums. If something has changed, the affected unit or module must be recompiled. If nothing has changed, the preprocessor states can not have changed as well.

> > That allows the compiler to auto-find the compilation order..

A compilation order only applies to units. A Pascal compiler or linker starts with the root unit, and then descends into all used units. C projects instead consist of a single (flat) list of modules, which can be compiled in any order.

> A C project doesn't require any compilation order, every module can be > compiled independently from other modules. Implementation: yes. Header: no.

Hmm, perhaps we have a different idea of precompiled header files. Let me explain what BC 3.1 did:

Header files are not precompiled for themselves, one by one, instead there must exist something like a root module (*.c) which contains a list of #include's. These included files are precompiled together, in the given order.
Multiple modules can share the same precompiled headers only if the modules #include the same header files in the same sequence.

The precompiled header files, for all modules of a project, reside in the same precompiled file. This file in fact is a library, containing distinct sets of precompiled headers, in different extent or order. IOW modules with exactly the same #include's in sequence share a single precompiled header module. The file may be organized as a tree of header modules, with bifurcations when different files are #included after a preceding identical sequence.

Borland also introduced a #pragma headerstop, that delimits the list of precompiled header files. All modules with the same #include's before this pragma can share the same header module, eventually following #includes are not precompiled. This feature allows to precompile a reasonable number of header files into one header module. AFAIR it was easier to #include exactly the same header files in all modules of a project, regardless of the actual needs of every module. The resulting precompiled header file, for a Win3.1 project, then could be as small as 20 MB, in contrast to > 80 MB for less, but different, #includes in the source modules.

Regardless of such optimization, as long as the header files are not touched themselves, and the #include's in the source modules are unchanged, a recompilation of the header files is never required.

DoDi

Reply by email