Sign in Join

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

Mailing lists / fpc-devel / 2011-11

Generics - a general question

6 messages · 2 people · –

Reply by email Subscribe to this list Your subscription

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

AvP

I just made a first attempt to use generics. Trying different things I figured out that the appended code compiles without any error message in FPC 2.6.0RC1

1. Is this behavior intended (generic objects)?
I didn't find anything like that in the docs

2. Replacing "object" at (1) with "class" gives an error when compiling

3. In my case the specialization should only work if _ResType is TType or derived, as certain fields and functions/procedures are read / executed

Is there currently a way to do this typesafe (my version is a hack, I believe) ?
If not, maybe this would be an interesting feature for FPC.

Maybe I'm trying to do the manager in a way, that isn't the right one - other suggestions are welcome - but as this is on a developer maillist (I know), my question mainly concerns the general generics part.

Thanks
A.v.P.

============================================================
program Test;
{$mode objfpc}{$H+}
uses Classes;
{$R *.res}
type
 TType = object
     RefCount : Word;
     constructor Init;
     destructor Done;
     function GetCount : Word;
 end;
 // manages a list of _ResType-Objects
 generic TManager<_ResType> = object  //(1)
       // _ResType must be TType or detrieved, howto define this??
   type
     PResType = ^_ResType;
   public
     constructor Create;
     destructor Destroy;
     function GetRefCount : Word;
     // stuff to manage Resource-array
   private
     Resource : array of PResType;   // array of pointer to specialized type
of generic
 end;
 TResManager = specialize TManager<TType>;
 constructor TType.Init;
 begin
   RefCount := 3;
   WriteLn('TType.Init done...');
 end;
 destructor TType.Done;
 begin
   WriteLn('TType.Done...');
 end;
 function TType.GetCount : Word;
 begin
   Result := Self.RefCount;
 end;
 constructor TManager.Create;
 begin
   SetLength(Resource, 2);
   Resource[0]^.Init;  // No problem here
   WriteLn('Manager.Create done...');
 end;
 destructor TManager.Destroy;
 begin
   Resource[0]^.Done;  // No problem here
   WriteLn('Manager.Destroy done...');
 end;
 function TManager.GetRefCount : Word;
 begin
   Result := Resource[0]^.RefCount;          // direct access
   // or:
   //Result := Resource[0]^.GetCount;        // helper function
 end;
Var
 TT : TResManager;
begin
 TT.Create;
 WriteLn(TT.GetRefCount);
// if commented then 'no problem' else 'Access violation'
 TT.Destroy;
end.

============================================================

An unhandled exception occurred at $0000000000411D90 : EAccessViolation : Access violation
$0000000000411D90 line 66 of Test.lpr
$0000000000411DF6 line 76 of Test.lpr

Reply by email

Sven Barth
to AvP
AvP wrote
1. Is this behavior intended (generic objects)? I didn't find anything like that in the docs

It's at least not forbidden. I can imagine though that it was forgotten to document as objects aren't used by that many persons (and thus they are forgotten rather easily). They are rather similary handled by the compiler as "class" (at least in regards to parsing; the code generated is of course different).

2. Replacing "object" at (1) with "class" gives an error when compiling

Simply replacing "object" by "class" is not sufficient, because classes need to be used differently than objects. If you need help regarding this, I can provide you one if you want or you can read this: http://wiki.freepascal.org/Object_Oriented_Programming_with_FreePascal_and_Lazarus

Show quoted text 6 lines
3. In my case the specialization should only work if _ResType is TType or derived, as certain fields and functions/procedures are read / executed Is there currently a way to do this typesafe (my version is a hack, I believe) ? If not, maybe this would be an interesting feature for FPC.

Currently this is not possible, but I plan to add that, because of Delphi compatibility. The feature that you want is called "constraints" and the declaration of "TManager" will then look like this:

generic TManager<_ResType: TType> = object
...
end;

The compiler will then also be able to check whether you are using methods that are valid for the passed type.

Regarding your code:

constructor TManager.Create; begin SetLength(Resource, 2);
Resource[0] := New(PResType, Init); // You need to allocate the memory
for the object
//>     Resource[0]^.Init;  // No problem here
Show quoted text 5 lines
WriteLn('Manager.Create done...'); end; destructor TManager.Destroy; begin
Dispose(Resource[0], Done); // Free the memory allocated earlier
//>     Resource[0]^.Done;  // No problem here
WriteLn('Manager.Destroy done...'); end;

Please note: While this is how it SHOULD work this does currently not work with any version of the compiler except maybe my generic branch (I haven't tested that yet).

As a workaround you can do this:

In TManager.Create:
Resource[0] := New(PResType);
Resource[0]^.Init;
In TManager.Destroy:
Resource[0]^.Dispose;
Dispose(Resource[0]);

This will issue two warnings though, but it works.

Regards,
Sven

Reply by email

Sven Barth
to Sven Barth
Sven Barth wrote
Please note: While this is how it SHOULD work this does currently not work with any version of the compiler except maybe my generic branch (I haven't tested that yet).

Ok, I have tested that with my branch and it doesn't work there either. After thinking a bit about that, it's logical and expected. So for my solution written in the previous mail to work we need the generic constraints.

Regards,
Sven

Reply by email

AvP
to Sven Barth: It's at least not forbidden. I can imagine though that it was forgotten to document as…

First of all, thanks for your answer.
I tried to make the example as simple as possible - and simply forgot to allocate the memory :$

Simply replacing "object" by "class" is not sufficient, because classes need to be used differently than objects.

Right, it actually needs a bit of modification in the destructor. ;-)

objects aren't used by that many persons

As I started / learned programming with TurboPascal, I habitually use objects, but is there a big difference for "the normal user", if advanced class features aren't used?
Is there a difference in speed / memory consumption / code size between objects and classes?

Back to generics:
How much does a specialized generic class / object differ from a class / object, that is directly programmed (not as generic), in the binary code ?

The reason for my question is that I often read that you shouldn't use generics if a programm heavily depends on speed (like 3D-games). So is this really an issue in FreePascal (like it seems to be in C-like languages) and how big is the difference in speed - if there is any?

Many questions, but hopefully it will make things more clear to me.

Thanks
A.v.P.

Reply by email

Sven Barth
to AvP
AvP wrote
> Simply replacing "object" by "class" is not sufficient, because classes > need to be used differently than objects. Right, it actually needs a bit of modification in the destructor. ;-)

I haven't yet tried to replicate your example with classes, so I can't say what exactly needs to be changed.

Show quoted text 6 lines
> objects aren't used by that many persons As I started / learned programming with TurboPascal, I habitually use objects, but is there a big difference for "the normal user", if advanced class features aren't used? Is there a difference in speed / memory consumption / code size between objects and classes?

The instance sizes themselves should be rather similar between classes and objects, but for every class type always a VMT is created, while for objects at least one virtual method needs to exist for that. Also classes should be more easy to use as they are implicit pointers. See here:

=== source begin ===

type
   TSomeObject = object
   end;
   PSomeObject = ^TSomeObject;
   TSomeClass = class
   end;
var
   o: PSomeObject;
   c: TSomeClass;
begin
   o := New(PSomeObject);
   Dispose(o);
   c := TSomeClass.Create;
   c.Free;
end.

=== source end ===

The advantage (if one needs that) of objects is that they can live on the stack (like records) and that you can declare constant objects (like records).

Speedwise classes and objects should be rather similar.

Classes can make use of Runtime Type Information. Thus you can query the name of the class by just using "c.ClassName" or for the class type by "c.ClassType". I don't know whether the "is" and "as" operators work for objects as well, but with them you can check whether a instance variable contains a certain subclass (is) or you can do a checked typecast (as) which raises an exception if the left side is not a subclass of the right side. Also classes can contain interfaces thus you can program to an interface instead of an implementation (which is sometimes seen as a cleaner way of object oriented programming).

Show quoted text 10 lines
Back to generics: How much does a specialized generic class / object differ from a class / object, that is directly programmed (not as generic), in the binary code ? The reason for my question is that I often read that you shouldn't use generics if a programm heavily depends on speed (like 3D-games). So is this really an issue in FreePascal (like it seems to be in C-like languages) and how big is the difference in speed - if there is any?

I don't know how generics are implemented in C(++), but in FPC it basically works like this:

* compiler parses a generic, but instead of generating code it stores all tokens in a token buffer inside the PPU file
* compiler parses a specialization; for this it loads the token buffer from the PPU file and parses that again, but replaces the generic parameters (in most examples "T", in your example "_ResType") by the given type (e.g. LongInt, TObject, or your TType). Thus the code basically appears as if you had written the same class as the generic but with T replaced by your given type.

So in theory there should be no speed differences.

Regards,
Sven

Reply by email