Sign in to follow this  
InvalidPointer

Fun With C++ [Beginners Stay Away, Lest Ye Be Corrupted]

Recommended Posts

InvalidPointer    1842
Have more time off today, and decided I'd test an interesting little idiom out to help with const-correctness. It turns out you can actually do a lot of neat (and admittedly very dangerous) tricks with placement new, up to and including re-instantiating objects using the this pointer. As far as I can tell, there do not appear to be any adverse effects so long as the idiom itself is used correctly; since we're using the this pointer from an already-constructed object we're guaranteed to have proper alignment and size and recreating the object in this fashion doesn't appear to muck up any lifetimes, etc. Code: main.cpp
#include "SomeClass.h"
#include <iostream>

int main()
{
	{
		SomeClass stackClass;
		stackClass.ReInstantiate();
		stackClass.WriteSomeData();
	}

	std::cout << "---" << std::endl;
	SomeClass* heapPtr = new SomeClass();
	heapPtr->ReInstantiate();
	heapPtr->WriteSomeData();
	delete heapPtr;

	int i;
	std::cin >> i;
	return 0;
}
SomeClass.h:
#pragma once

class SomeClass
{
public:
	SomeClass();
	virtual ~SomeClass();

	void				ReInstantiate();

	void				WriteSomeData();

private:
	char* const			dataChunk;
	const unsigned long		moreData;
	const bool			evenMoreData;
};
SomeClass.cpp:
#include "SomeClass.h"
#include <memory>
#include <iostream>

SomeClass::SomeClass() : dataChunk(new char[32]),
						 moreData(0),
						 evenMoreData(true)
{
	std::cout << "SomeClass()" << std::endl;
}

SomeClass::~SomeClass()
{
	std::cout << "~SomeClass()" << std::endl;
	if(dataChunk)
		delete[] dataChunk;
}

void SomeClass::WriteSomeData()
{
	std::cout << "WriteSomeData()" << std::endl;
	for( size_t i = 0; i < 32; i++ )
	{
		dataChunk[i] = '3';
	}
}

void SomeClass::ReInstantiate()
{
	std::cout << "ReInstantiate()" << std::endl;
	//void* thisPtr = this;
	this->~SomeClass();
	new(this) SomeClass;
}
I would appreciate if someone could test this on something other than MSVC9 (VS2008) to see what happens. No guarantees if it's actually even practical or actually works elsewhere, it just seems interesting.

Share this post


Link to post
Share on other sites
iMalc    2466
Quote:
Original post by DevFred
I'm pretty sure I once read an article by Herb Sutter in which he discussed this as a bad idea.
You would be referring to GotW #23, which InvalidPointer should have a good read of.

Share this post


Link to post
Share on other sites
InvalidPointer    1842
Quote:
Original post by iMalc
Quote:
Original post by DevFred
I'm pretty sure I once read an article by Herb Sutter in which he discussed this as a bad idea.
You would be referring to GotW #23, which InvalidPointer should have a good read of.

I actually did manage to dig that up before you posted the link (through the somewhat related GotW #28) and it did validate a few reservations/sticky spots I had regarding inheritance. Obviously, if you call this method on a derived object it's going to die; you aren't even instantiating the same object type! There is something of a workaround, however-- by making the method virtual and fixing types in derived classes it does work correctly. While we're on the subject of bad C++ practice, you could probably even make the ReInstantiate function definition a macro, though I'm sure there might be more esoteric methods of automagically updating this.

Again, no guarantees if it's actually even practical or actually works elsewhere, it just seems interesting.

Share this post


Link to post
Share on other sites
Moomin    332
Quote:
Original post by InvalidPointer
There is something of a workaround, however-- by making the method virtual and fixing types in derived classes it does work correctly.

Except the v-table could be destroyed in the destructor meaning a copy would be needed?

Share this post


Link to post
Share on other sites
InvalidPointer    1842
Quote:
Original post by Moomin
Quote:
Original post by InvalidPointer
There is something of a workaround, however-- by making the method virtual and fixing types in derived classes it does work correctly.

Except the v-table could be destroyed in the destructor meaning a copy would be needed?

I believe that placement new renders that moot, as it just builds a new one anyway. Actually implementing my proposed solution and calling ReInstantiate on a base class pointer seems to work, though again that may just be result of compiler-specific behavior.

EDIT: Never mind, I see what you're getting at now, and no, it wouldn't be an issue. At this point we've already dispatched the recreation method to the right implementation, and we know at compile time what type of class we want to create-- it's 'baked into' the function itself.

Share this post


Link to post
Share on other sites
Nitage    1107
While it's interesting that it can be done, I agree with Harb Sutter that it shouldn't be done.

Quote:
I still don't see what this has to do with const-correctness.

It allows a reasonable copy constructor/assignment operator to be defined for classes with const member variables (and reference member variables).

Share this post


Link to post
Share on other sites
visitor    643

void SomeClass::ReInstantiate()
{
std::cout << "ReInstantiate()" << std::endl;
//void* thisPtr = this;
this->~SomeClass();
new(this) SomeClass;
}


I wonder: if the constructor by any chance happens to throw, would it mean that the destructor would be called again during stack unwinding?

Share this post


Link to post
Share on other sites
frob    44908
Quote:
Original post by Nitage
While it's interesting that it can be done, I agree with Harb Sutter that it shouldn't be done.

That is true of many details of C++.


Language lawyers can legally inject code into a class that uses templates. They can abuse the lack of strict type checking to legally expose details that are meant to be ignored. They can legally abuse macros, legally abuse the many forms of new, and legally abuse just about any other feature you can imagine.

Write to be understood.

If the process is best understood using simple constructs, write it simply. If the process is best understood by a language-lawyer technique, then use it, but acknowledge that few people will understand the nuance and that it likely will become a source of bugs.

Share this post


Link to post
Share on other sites
iMalc    2466
Quote:
Original post by Nitage
While it's interesting that it can be done, I agree with Herb Sutter that it shouldn't be done.
As do I.
I don't believe any good ever comes from using the object rebirth techinque.
"Here be dragons." indeed!

Share this post


Link to post
Share on other sites
theOcelot    498
With all due respect to those raising legitimate issues, I think you're missing the point. The title was "Fun with C++", not "Sober production code in C++".

Share this post


Link to post
Share on other sites
Rydinare    487
Fun thread. Good intentions; bad results. This is why I both love and hate C++ sometimes. I can appreciate the subtlety and creativity of attempting a solution like this, along with the understanding that the known caveats make this generally not worth it. However, I know plenty of people who would look at this, say, "GOOD IDEA!" even with the caveats listed and down the rabbit hole we go. Later, there will be this witch hunt for, "why does this mysteriously not work anymore? Why can't Microsoft make a compiler that generates good code??? etc etc.." [grin]

Share this post


Link to post
Share on other sites
Red Ant    471
Quote:
Original post by Nitage
It allows a reasonable copy constructor/assignment operator to be defined for classes with const member variables (and reference member variables).


An assigment operator for classes with const members? Isn't that actually a violation of const correctness?

Share this post


Link to post
Share on other sites
cache_hit    614
As with most things in C++, everything is useful sometimes.

I believe boost::variant is implemented using this technique, and I would be extremely surprised if other parts of boost WEREN'T. I'm not saying you should go out and use it all over the place, or even anywhere at all for that matter. But there's always a use case.

Share this post


Link to post
Share on other sites
iMalc    2466
Quote:
Original post by cache_hit
As with most things in C++, everything is useful sometimes.

I believe boost::variant is implemented using this technique, and I would be extremely surprised if other parts of boost WEREN'T. I'm not saying you should go out and use it all over the place, or even anywhere at all for that matter. But there's always a use case.
Nope, a quick google tells me:
Quote:
All members of variant satisfy the strong guarantee of exception-safety.
which means it can't possibly use what I'd call the object rebirth technique. It would almost certainly use the copy and swap idiom.

Frankly I'd be very surprised if any parts of boost used it.

Oh and I understand perfectly well that this thread had to word "fun" in the title. Just bear in mind that what some people consider fun, others simply cringe at.

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by iMalc
Quote:
Original post by cache_hit
As with most things in C++, everything is useful sometimes.

I believe boost::variant is implemented using this technique, and I would be extremely surprised if other parts of boost WEREN'T. I'm not saying you should go out and use it all over the place, or even anywhere at all for that matter. But there's always a use case.
Nope, a quick google tells me:
Quote:
All members of variant satisfy the strong guarantee of exception-safety.
which means it can't possibly use what I'd call the object rebirth technique. It would almost certainly use the copy and swap idiom.

Frankly I'd be very surprised if any parts of boost used it.

Oh and I understand perfectly well that this thread had to word "fun" in the title. Just bear in mind that what some people consider fun, others simply cringe at.


Yes, but the way boost::variant implements the strong guarantee is by using a backing store of memory allocated on the heap. Besides, there's certainly ways to augment the above code to provide the strong guarantee. Just saying that they use the technique doesn't mean they use those exact 2 lines of code with no try/catch wrappers and error handling in case of exceptions.

This comes from the documentation of boost::variant.

Quote:

Since variant manages its content on the stack, the left-hand side of the assignment (i.e., v1) must destroy its content so as to permit in-place copy-construction of the content of the right-hand side (i.e., v2). In the end, whereas v1 began with content of type T, it ends with content of type U, namely a copy of the content of v2.

The crux of the problem, then, is this: in the event that copy-construction of the content of v2 fails, how can v1 maintain its "never-empty" guarantee? By the time copy-construction from v2 is attempted, v1 has already destroyed its content!


Read on to learn the solution to the problem. I haven't actually looked at the source code, but it sure sounds to me like they're doing this. Definitely in the case where both sides of the assignment contain the same type, and highly similar in the case where they aren't.



Edit: Got confused with various types of "guarantees". Boost.variant actually does not provide the strong guarantee on assignment from what I can tell

Share this post


Link to post
Share on other sites
visitor    643
Placement new and explicit destructor calls naturally do have a place, but even boost::variant doesn't do it with itself.

Quote:

It allows a reasonable copy constructor/assignment operator to be defined for classes with const member variables (and reference member variables).


For const members you could use a const_cast (which should show how fishy the idea is conceptually) and for reseatable reference members you could use pointers.

Share this post


Link to post
Share on other sites
DevFred    840
What's the point of making a member const if you intend to modify it? That's like making something private and complain that it isn't public...

Share this post


Link to post
Share on other sites
SiCrane    11839
Quote:
Original post by cache_hit
I haven't actually looked at the source code, but it sure sounds to me like they're doing this.

I have and they don't.

Share this post


Link to post
Share on other sites
Nitage    1107
Quote:
For const members you could use a const_cast (which should show how fishy the idea is conceptually) and for reseatable reference members you could use pointers.


Yes, you could - and you probably should. I was just suggesting why some people think this is a good idea.

Quote:
I don't believe any good ever comes from using the object rebirth techinque.

But isn't it the technique that std::vector uses to seperate construction/destruction from allocation/dealloaction?

Object rebirth:
aVector.pop_back();
aVector.push_back(my_struct());

Share this post


Link to post
Share on other sites
SiCrane    11839
Quote:
Original post by Nitage
But isn't it the technique that std::vector uses to seperate construction/destruction from allocation/dealloaction?

No, when you call pop_back() it invalidates all iterators, pointers and references to the last element. If you follow a pop_back() with a push_back() those are two different objects. And more importantly, std:vector doesn't destroy itself just to recreate itself in the same location.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this