Jump to content

  • Log In with Google      Sign In   
  • Create Account


#ActualHodgman

Posted 15 December 2012 - 07:58 AM

I shy away from malloc()/free() as well; much of my object's constructors accept a pointer to an optional block of preallocated memory, so most objects that I use are allocated on the stack to be reclaimed when I go out of scope.

In C++, the equivalent is "placement new", which is how you manually call a constructor on a block of memory (whereas regular new allocates memory and calls the constructor). However, you've got to be very careful doing this kind of stuff, if you want to keep regular C++ behaviour -- destructors won't be called when the memory goes out of scope, so when using placement-new, you must also manually call the destructors of your objects at the appropriate times.

How do you pull off having a custom new? I'm concerned that others' modules that I might use would rely on new having default behavior

I linked to the code above, that bit is open source Posted Image
Instead of overriding the new operator with my own version, I've chosen to invent my own keyword via a macro - eiNew (ei is my engine's "macro prefix", short for "eight", the name of the engine). If I use any 3rd party code that relies on new, then it behaves as usual.
My eiNew macro uses a stack allocator to grab enough memory for the new object, then uses placement new to construct the object in that area. It also adds the address of the object to a linked-list belonging to a "scope" object (which is a RAII-type object), which is used to call the destructor when the scope is destructed.

I also have an eiAlloc macro, which does the same thing, but without calling constructors/destructors.

e.g. Using the built-in call-stack, we can write nice C++ code like:
class Foo { ... };
{
 Foo obj1( 42 );//increase "the call stack" by sizeof(Foo), call constructor with arg "42"
 Foo obj2( 1337 );//again
}//obj2 and obj1 are out of scope, and are destructed
But for cases where I want to use memory other than the built-in call stack, my eiNew macro mimics this regular behaviour for my own buffers:
char buffer[1024];
StackAlloc stack( buffer, 1024 );
{//*marker
 Scope a( stack ); // N.B. Scope objects could also be created with eiNew, instead of being created in the call-stack
 Foo* obj1 = eiNew( a, Foo )( 42 );//increases stack's pointer by sizeof(Foo), constructs a Foo with param "42", adds obj into the scope's destruction list
 Foo* obj2 = eiNew( a, Foo )( 1337 );//again...
}//"a" has gone out of scope, obj2 and obj1 have their destructors called, the stack pointer in "stack" is reset back to where it was at "*marker"
Most of the time when I use malloc, it's to grab big buffers, like "buffer" above, and then I use eiNew for everything.


These ideas for making C++ (constructors/destructors) interact nicely with the simplicity of "stack allocators" comes from the "scope/stack" link in my last post.

#1Hodgman

Posted 15 December 2012 - 07:55 AM

I shy away from malloc()/free() as well; much of my object's constructors accept a pointer to an optional block of preallocated memory, so most objects that I use are allocated on the stack to be reclaimed when I go out of scope.

In C++, the equivalent is "placement new", which is how you manually call a constructor on a block of memory (whereas regular new allocates memory and calls the constructor). However, you've got to be very careful doing this kind of stuff, if you want to keep regular C++ behaviour -- destructors won't be called when the memory goes out of scope, so when using placement-new, you must also manually call the destructors of your objects at the appropriate times.

How do you pull off having a custom new? I'm concerned that others' modules that I might use would rely on new having default behavior

I linked to the code above, that bit is open source Posted Image
Instead of overriding the new operator with my own version, I've chosen to invent my own keyword via a macro - eiNew (ei is my engine's "macro prefix", short for "eight", the name of the engine). If I use any 3rd party code that relies on new, then it behaves as usual.
My eiNew macro uses a stack allocator to grab enough memory for the new object, then uses placement new to construct the object in that area. It also adds the address of the object to a linked-list belonging to a "scope" object (which is a RAII-type object), which is used to call the destructor when the scope is destructed.

I also have an eiAlloc macro, which does the same thing, but without calling constructors/destructors.

e.g. Using the built-in call-stack, we can write nice C++ code like:
class Foo { ... };
{
 Foo obj1( 42 );//increase "the call stack" by sizeof(Foo), call constructor with arg "42"
 Foo obj2( 1337 );//again
}//obj2 and obj1 are out of scope, and are destructed
But for cases where I want to use memory other than the built-in call stack, my eiNew macro mimics this regular behaviour for my own buffers:
char buffer[1024];
StackAlloc stack( buffer, 1024 );
{//*marker
 Scope a( stack ); // N.B. Scope objects could also be created with eiNew, instead of being created in the call-stack
 Foo* obj1 = eiNew( a, Foo )( 42 );//increases stack's pointer by sizeof(Foo), constructs a Foo with param "42", adds obj into the scope's destruction list
 Foo* obj2 = eiNew( a, Foo )( 1337 );//again...
}//"a" has gone out of scope, obj2 and obj1 have their destructors called, the stack pointer in "stack" is reset back to where it was at "*marker"
Most of the time when I use malloc, it's to grab big buffers, like "buffer" above, and then I use eiNew for everything.

PARTNERS