Results 1 to 10 of 25

Thread: CLASS vs. OBJECT and memory management

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Quote Originally Posted by Ñuño Martínez View Post
    By the way, using an array of OBJECTs prevents you to use virtual methods, as all OBJECT on the array will be the same type.
    Not really. When using virtual methods, each object must also contain a "secret" member that points to the VMT. Setting up this pointer is done by the constructor, so if you typecast some of the objects in the array and call the child constructor on them, the VMT will be properly initialized to that of the child class. One important thing to note here, though, is that the descendant classes cannot declare any new fields, since they would be located after the base class data, thus accessing them would go past the base class memory area and mess stuff up in the next array member.

  2. #2
    Quote Originally Posted by SilverWarior View Post
    (...)
    I think I don't understand your code. Extending TSprites will duplicate the sprite list, and I don't get benefits. And it's worst with the TPlayer class. I mean, create a "TPlayer" object will create a new list of sprites, doesn't it?

    Quote Originally Posted by Super Vegeta View Post
    Not really. When using virtual methods, each object must also contain a "secret" member that points to the VMT. Setting up this pointer is done by the constructor, so if you typecast some of the objects in the array and call the child constructor on them, the VMT will be properly initialized to that of the child class. One important thing to note here, though, is that the descendant classes cannot declare any new fields, since they would be located after the base class data, thus accessing them would go past the base class memory area and mess stuff up in the next array member.
    You're right. If descendant classes use the same data than the parent ones then there's no problems. I see it. The problem is how to know which typecast to use. May be a "SpriteType" field will do the work but I find it quit ugly:
    Code:
      FOR Ndx := LOW (SpriteList) TO HIGH (SpriteList) DO
        CASE SpriteList[Ndx].SprType OF
          stPlayer : TSprPlayer (SpriteList[Ndx]).Update;
          stBullet : TSprBullet (SpriteList[Ndx]).Update;
          stEnemy1 : TSprEnemy1 (SpriteList[Ndx]).Update;
          stEnemy2 : TSprEnemy2 (SpriteList[Ndx]).Update;
          ELSE SpriteList[Ndx].Update;
        END;
    Using a "CLASS OF ..." field may fix the problem, but I think it's not possible...
    Code:
      TYPE
        TSprite = CLASS; { Forward declaration. }
    
        TSpriteClass = CLASS OF TSprite;
    
        TSprite = CLASS (TObject)
        PRIVATE
        { ... }
          fSpriteClass: TSpriteClass;
        PUBLIC
        { ... } 
    
          PROPERTY SprClass: TSpriteClass READ fSpriteClass;
        END;
    
    { ... }
        FOR Ndx := LOW (SpriteList) TO HIGH (SpriteList) DO
        { This doesn't work. }
          SpriteList[Ndx].SprClass (SpriteList[Ndx]).Update;
    Anyway, I'm reading Game Programming Patterns (you should read it even if you don't like patterns, actually I don't like them but the book gives a lot of good ideas) and I've found some information. What I'm asking for is an Object Pool, as you have all objects and reuses them when needed. This is the way Build works, for example.

    Also I've found a problem about microprocessor caches: if using a scripting (bytecode ) to define the behavior of sprites (allowing a way to keep size of all TSprite while extending them), anytime a script is executed it may kill the cache.
    No signature provided yet.

  3. #3
    Quote Originally Posted by Ñuño Martínez View Post
    If descendant classes use the same data than the parent ones then there's no problems. I see it. The problem is how to know which typecast to use.
    Unless you want to dynamically switch an array member between types, then you can just call the adequate constructor when creating the member and it will work fine.
    Code:
    program aaa; {$MODE OBJFPC}
    
    type bbb = object 
    	public procedure hello; virtual;
    	public constructor create;
    	public destructor destroy; virtual;
    end;
    
    type ccc = object(bbb) 
    	public procedure hello; virtual;
    	public destructor destroy; virtual;
    end;
    
    procedure bbb.hello;
    begin
    	writeln('bbb: hello')
    end;
    
    constructor bbb.create; 
    begin
    	writeln('bbb: create')
    end;
    
    destructor bbb.destroy;
    begin
    	writeln('bbb: destroy')
    end;
    
    
    procedure ccc.hello;
    begin
    	writeln('ccc: hello')
    end;
    
    destructor ccc.destroy;
    begin
    	writeln('ccc: destroy')
    end;
    
    
    Var
    	X: Array[0..1] of bbb;
    
    begin
    	X[0].Create();
    	ccc(X[1]).Create();
    	
    	X[0].Hello();
    	X[1].Hello();
    	
    	X[0].Destroy();
    	X[1].Destroy()
    end.
    Although the compiler says that "X is not initialized" during "X[0].Create()", the above code (with FPC 2.6.4) works fine - after the constructors are called, the VMT pointers are set up, so calls to Hello() and Destroy() are properly resolved to those of the child class:
    Code:
    bbb: create
    bbb: create   // "ccc" did not have an explicit constructor, so the "bbb" constructor was called, but the VMT is still set up properly
    bbb: hello
    ccc: hello
    bbb: destroy
    ccc: destroy
    IMO, with what you're trying to achieve, it would actually be simpler to just use records and have a "spriteType" field there. You can use the Case() statement to test for the type and call the appropriate procedure when needed. It may not look the prettiest, but it's the simplest way and there's no risk that compiler optimisations mess something up.
    Also, you can have a SPRITETYPE_NONE value for your spriteType, which gives you a way to have "NULL" array members for free.

    Also, have you tried using a profiler? It may turn out that the memory accesses aren't really that much of a problem and you're trying to optimize the wrong thing.
    Last edited by Super Vegeta; 15-06-2016 at 04:32 PM. Reason: Expand with note on using records

  4. #4
    Quote Originally Posted by Ñuño Martínez View Post
    I think I don't understand your code. Extending TSprites will duplicate the sprite list, and I don't get benefits.
    Not necessarily. It depends on how you are extending the TSprites class. If you are extending it by just adding additional methods to descendant class like in TMovableSprite it won't duplicate list since you are still accessing it through inherited methods or properties from parent class.

    Quote Originally Posted by Ñuño Martínez View Post
    And it's worst with the TPlayer class. I mean, create a "TPlayer" object will create a new list of sprites, doesn't it?
    Yes in case of TPlayer I do create a new array to store additional information that is not being stored in ASprites array.

    What is advantage of this?

    For instance during rendering phase you only need information about sprites positions, size and orientation. You don't need additional information that you might be storing in your objects like in-game unit health.
    Now if you are storing all that information in one array when the data from that array is put on stack the stack would contain the data that you need during the rendering phase and also the data that you don't need during rendering phase. So you will be able to fill the stack with information for limited number of units at once.
    But if you have one array which basically contains only data that that is needed during rendering phase you would be able to store relevant data for greater number of in-game units at once.

    On the other hand in a different part of your code you might only need data related to in-game units health (checking which units are dead, regenerating their health). Here it would also be beneficial if you only put relevant data about units health on the stack without other unneeded data like unit positions etc.

    Perhaps it would be more understandable if I create a mind graph representation of the concept

  5. #5
    Quote Originally Posted by Super Vegeta View Post
    IMO, with what you're trying to achieve, it would actually be simpler to just use records and have a "spriteType" field there. You can use the Case() statement to test for the type and call the appropriate procedure when needed. It may not look the prettiest, but it's the simplest way and there's no risk that compiler optimisations mess something up.
    Also, you can have a SPRITETYPE_NONE value for your spriteType, which gives you a way to have "NULL" array members for free.
    This is much like how Build works, and I think I'll go this way.

    Quote Originally Posted by Super Vegeta View Post
    Also, have you tried using a profiler? It may turn out that the memory accesses aren't really that much of a problem and you're trying to optimize the wrong thing.
    No, I didn't. I tried to use GNU's profiler a long time ago (+5 years) and it didn't work too much as it couldn't identify several Object Pascal stuff. It was useless. I should try again, may be FPC 3.0 works. [edit] I've just read at the Lazarus wiki that "Support for using gprof under Linux is broken in FPC 2.2.0-2.4.0; it is fixed in FPC 2.6+", which explains why it didn't work.

    Quote Originally Posted by SilverWarior View Post
    Perhaps it would be more understandable if I create a mind graph representation of the concept
    That would be great because I'm a bit lost.
    Last edited by Ñuño Martínez; 16-06-2016 at 04:39 PM.
    No signature provided yet.

  6. #6
    Can I read this thread as allegory to Object vs Record (with methods)

  7. #7
    Of course you can.

    By the way, I've discovered "records with methods" recently. May be they are a better approach for what I want/need as they don't need initialization/constructor and no inheritance is involved (AFAIK FPC OBJECT inherits from an internal TObject the same way CLASS does, doesn't it?).
    Last edited by Ñuño Martínez; 19-06-2017 at 12:03 PM. Reason: typo
    No signature provided yet.

  8. #8
    You definitely should use records, with the {$modeswitch AdvancedRecords} define set. The classic Turbo Pascal style "objects" are well known to be buggy (memory leaks, e.t.c.) and as far as I know they aren't being actively maintained or developled in either Delphi or FPC, and I see no reason why they ever would be again in the future.

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •