Placement new

Started by
16 comments, last by vreality 10 years, 7 months ago

Obviously I'm missing something here, so I'll post in the beginner's forum.

Query:

Why would I ever use placement new?

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
Advertisement
If you're doing regular high-level programming, you'd likely never need to use it.

If you're doing low-level systems work and implementing a memory allocator, then you might use it to construct a C++ object inside some pre-allocated area.

Regular-new does two things -- it actually allocates some memory (like malloc) and then also calls the constructor to generate a valid C++ object.
Regular-delete does two things too -- it frees this allocation (like free), but first also calls the destructor to clean up the C++ object.

Placement-new lets you separate these two tasks. You can then handle the allocation part however you want, and then use placement-new to construct a valid C++ object inside your custom allocation. You can't use delete on these objects (because they weren't allocated using regular-new) so you have to destruct them manually.

e.g. say you had a fixed block of 1KB pre-allocated, but you wanted to use some C++ objects inside that memory block. A stack allocator would boil down to something like this:
//simple stack allocator, deconstructed:
	char stack[1024];
	int size = 0;

	struct Obj { Obj() {...} ~Obj() {...} };

//manually construct 3 objects inside the pre-allocated memory
	Obj* foo = new(&stack[size]) Obj;   size += sizeof(Obj);
	Obj* bar = new(&stack[size]) Obj;   size += sizeof(Obj);
	Obj* baz = new(&stack[size]) Obj;   size += sizeof(Obj);

//manually destruct them when done
	foo->~Obj();
	bar->~Obj();
	baz->~Obj();

	size = 0;
This is especially important if the 'Obj' class has any virtual methods. In that case, the constructor doesn't just run your code to initialize the members, it also initializes the vtable pointer, so that your virtual methods actually work wink.png

I actually make heavy use of it (hidden behind a macro) in my engine: http://eighthengine.com/2013/08/30/a-memory-management-adventure-2/

So if the ctor is called manually on a derived class it doesn't set up the vtable?

Edit:

Er... This thing isn't letting me call the ctor manually. Is that new or am I remembering incorrectly?

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

Placement new is what you would write to call the constructor manually for some memory you provide, if you would write only a constructor call it would mean something else like making a temporary or casting a value.

Btw. if one doesnt need normal new and placement new at same time for a single class one can also overwrite operator new and delete for that class and then new and delete it normally, without needing the weirdness of placement new and direct destructor calls.

As an example of a usage of placement new:

I create a placement new pointer to shared memory (on Linux systems). Shared memory, depending on implementation, can actually be placed directly in /dev/shm as a RAM disk. If you need two complete processes to share information it's one way of doing things.

IIRC I've seen implementations that use placement new on things like internal timers. As Hodgman points out, this is almost never a concern for high level programmers.

Edit: Clarifying the implementation of the RAM disk on Linux.

Besides the above, placement new is the only way to initialize non-POD union members. (POD unions can be used without initialization, by just using assignment.)

It can also be used (with care) to initialize memory-mapped I/O devices if you want a direct object representation of the memory that controls it. Sometimes its better to have an object representation which is a proxy for the object, but sometimes not. You can use placement new with careful construction in the latter case. I say careful because you have to make sure your class's data members line up with the in-memory registers, and you also have to initialize some of those registers in the manner and order that the hardware requires -- but its a neat parlor trick when the opportunity arises.

throw table_exception("(? ???)? ? ???");

It can also be used (with care) to initialize memory-mapped I/O devices if you want a direct object representation of the memory that controls it. Sometimes its better to have an object representation which is a proxy for the object, but sometimes not. You can use placement new with careful construction in the latter case. I say careful because you have to make sure your class's data members line up with the in-memory registers, and you also have to initialize some of those registers in the manner and order that the hardware requires -- but its a neat parlor trick when the opportunity arises.

Aye. I was wondering about that earlier. I've never really worked with such a device, but I was thinking that maybe that was the reasoning behind it.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

It can work, but I don't think I'd go as far as saying it was a critical part of the reasoning behind it. Enabling custom allocations like others have described is a damn fine reason all on its own. And like I said, you first have to ensure your data aligns to the registers, and then have to possibly initialize those registers in the correct order, and sometimes going back to write new values to them mid-way-through, in order to actually bring up the hardware device correctly. In C++ order of initialization by default is related to the order in which member variables are declared, so C++ is somewhat at odds with the needs of some hardware. You can work around it, of course, by having an init member-function but that negates some of the utility of classes. Its sometimes more practical to just write a macro that dereferences the right address and casts to a class representing the type, with a free-standing init function.

But definitely memory-mapped IO was on their minds in general, that's actually what bit-fields are for -- they're not meant so that you can pack your data (if they were, they're a half-baked solution for that).

throw table_exception("(? ???)? ? ???");

Note that while scalar (regular) placement new is useful, the array form is more or less useless. It won't reliably place the first object in the array at the address you give it, which pretty much defeats the purpose of using placement new in the first place.

This topic is closed to new replies.

Advertisement