views:

935

answers:

8

Hi. I want to reset an object. Can I do it in following way?

anObject->~AnObject();
anObject = new(anObject) AnObject();
// edit: this is not allowed: anObject->AnObject();

This code is obviously a subset of typical life cycle of an object allocated by in placement new:

AnObject* anObject = malloc(sizeof(AnObject));
anObject = new (anObject) AnObject(); // My step 2.
// ...
anObject->~AnObject(); // My step 1.
free(anObject)
// EDIT: The fact I used malloc instead of new doesn't carry any meaning

The only thing that's changed is the order of constructor and destructor calls.

So, why in the following FAQ all the threatening appear?

[11.9] But can I explicitly call a destructor if I've allocated my object with new?

FAQ: You can't, unless the object was allocated with placement new. Objects created by new must be deleted, which does two things (remember them): calls the destructor, then frees the memory.

FQA: Translation: delete is a way to explictly call a destructor, but it also deallocates the memory. You can also call a destructor without deallocating the memory. It's ugly and useless in most cases, but you can do that.

The destructor/constructor call is obviously normal C++ code. Guarantees used in the code directly result from the in placement new guarantees. It is the core of the standard, it's rock solid thing. How can it be called "dirty" and be presented as something unreliable?


EDIT:

Do you think it's possible, that the in-placement and non-in-placement implementation of new are different? I'm thinking about some sick possibility, that the regular new can for example put size of the memory block allocated before the block, which in-placement new obviously would not do (because it doesn't allocate any memory). This could result in a gap for some problems... Is such new() implementation possible?

+7  A: 

You cannot call the constructor in the manner indicated by you. Instead, you can do so using placement-new (like your code also indicates):

new (anObject) AnObject();

This code is guaranteed to be well-defined if the memory location is still available – as it should be in your case.

(I've deleted the part about whether this is debatable code or not – it's well-defined. Full stop.)

By the way, Brock is right: how the implementation of delete isn't fixed – it is not the same as calling the destructor, followed by free. Always pair calls of new and delete, never mix one with the other: that's undefined.

Konrad Rudolph
@Mehrdad: what ` I forgot that `anObject` was already a pointer.
Konrad Rudolph
No problem. I upvoted when you changed it.
Mehrdad Afshari
But if I need it (because the pointer to object being reset is keep in very long queue, which I don't want to parse), then there is obviously no problem, right?
Just to add to this answer: The whole reason placement new exists is because you can't call a constructor directly.
Dave Hinton
So what would happen if the placement new ctor call throws an exception? The memory won't get released, but the object isn't constructed either, so we can't call delete ourselves.
jalf
@jalf: if constructors throw exception, they need to clear up what they've already created – *always*, not just with placement new. After that, we don't need, and in fact mustn't, call `delete` on the object.
Konrad Rudolph
+5  A: 

Technically it is bad practice to call constructors or destructors explicitly.

The delete keyword ends up calling them when you use it. Same goes for new with constructors.

I'm sorry but that code makes me want to tear my hair out. You should be doing it like this:

Allocate a new instance of an object

AnObject* anObject = new AnObject();

Delete an instance of an object

delete anObject;

NEVER do this:

anObject->~AnObject(); // My step 1.
free(anObject)

If you must "reset" an object, either create a method which clears all the instance variables inside, or what I would recommend you do is Delete the object and allocate yourself a new one.

"It is the core of the language?"

That means nothing. Perl has about six ways to write a for loop. Just because you CAN do things in a language because they are supported does mean you should use them. Heck I could write all my code using for switch statements because the "Core" of the language supports them. Doesn't make it a good idea.

Why are you using malloc when you clearly don't have to. Malloc is a C method.

New and Delete are your friends in C++

"Resetting" an Object

myObject.Reset();

There you go. This way saves you from needlessly allocating and deallocating memory in a dangerous fashion. Write your Reset() method to clear the value of all objects inside your class.

Brock Woolf
I want to reset my object and I rely on core of the C++ standard. What's wrong with that?
See my answer for why.
Brock Woolf
The Reset() method requires maintenance.
If you wrote all your code the way you are proposing, I'd say your entire codebase would need maintenance
Brock Woolf
+5  A: 

Note that they're not malloc and free that are used, but operator new and operator delete. Also, unlike your code, by using new you're guaranteeing exception safety. The nearly equivalent code would be the following.

AnObject* anObject = ::operator new(sizeof(AnObject));
try
{
    anObject = new (anObject) AnObject();
}
catch (...)
{
    ::operator delete(anObject);
    throw;
}

anObject->~AnObject();
::operator delete(anObject)

The reset you're proposing is valid, but not idiomatic. It's difficult to get right and as such is generally frowned upon and discouraged.

avakar
+1: Good point about exception safety
James Hopkin
+15  A: 

Why not implement a Clear() method, that does whatever the code in the body of the destructor does? The destructor then just calls Clear() and you call Clear() directly on an object to "reset it".

Another option, assuming your class supports assignment correctly:

MyClass a;
...
a = MyClass();

I use this pattern for resetting std::stack instances, as the stack adaptor does not provide a clear function.

anon
Because it would require to maintain the Clear() method correctness.
You have to maintain the destructor "correctness" - I'm suggesting moving the destructor code into the Clear() method, so the destructor only contains a single call to Clear(). I write almost all my own code this way, BTW.
anon
Isn't it impossible to implement "destructive" Clear() method? It would have to free any heap allocated members when called from destructor. But when called as a reset-like method, it would have to only clear such members and leave them usable. This would require to implement Init() method and call it from constructor. Reset method would call Clear() and then Init(). Init methods look like non-RAII code. And what when multiple constructors are needed? This can complicate things.
It depends on the class's semantics. If rebuilding the class after clear is expensive, then use my second suggestion. I would say that either is likely to be less complicated than messing about with explicit destructor calls and use of placement new.
anon
+3  A: 

You can't call the constructor like that, but there's nothing wrong with reusing the memory and calling placement new, as long you don't delete or free (deallocate) the memory. I must say reseting an object like this is a little sketchy. I would write an object to be explicitly resetable, or write a swap method and use that to reset it.

E.g.

anObject.swap( AnObject() ); // swap with "clean" object
Logan Capaldo
This is the only real alternative IMO -- HandleBody + swap() with temporary.
+2  A: 

If your object has sensible assignment semantics (and correct operator=), then *anObject = AnObject() makes more sense, and is easier to understand.

EFraim
+1  A: 

It is far better just to add something like a Reset() method to your object rather than play with placement new's.

You are exploiting the placement new feature which is intended to allow you to control where an object is allocated. This is typically only an issue if your hardware has "special" memory like a flash chip. IF you want to put some objects in the flash chip, you can use this technique. The reason that it allows you to explicitly call the destructor is that YOU are now in control of the memory, so the C++ compiler doesn't know how to do the deallocation part of the delete.

It is not saving you much code either, with a reset method, you will have to set the members to their starting values. malloc() doesn't do that so you are going to have to write that code in the constructor anyway. Just make a function that sets your members to the starting values, call it Reset() call it from the constructor and also from anywhere else you need.

manovi
The real alternative is the HandleBody idiom and swap() method. Reset method is considered harmful by me -- it requires careful maintenance.
+6  A: 

Don't get sucked in by the FQA troll. As usual he gets the facts wrong.

You can certainly call the destructor directly, for all objects whether they are created with placement new or not. Ugly is in the eye of the beholder, it is indeed rarely needed, but the only hard fact is that both memory allocation and object creation must be balanced.

"Regular" new/delete simplifies this a bit by tying memory allocation and object creation together, and stack allocation simplifies it even further by doing both for you.

However, the following is perfectly legal:

int foo() {
    CBar bar;
    (&bar)->~CBar();
    new (&bar) CBar(42);
 }

Both objects are destroyed, and the stack memory is automatically recycled too. yet unlike the FQA claims, the first call of the destructor is not preceded by placement new.

MSalters
Succinct and correct.
Konrad Rudolph
Do you think it's possible, that the in-placement and non-in-placement implementation of new can differ? I'm thinking about some sick possibility, that regular new can for example put size of the memory block allocated before the block, and in-placement new obviously not (because it doesn't allocate any memory). This could result in a gap for some problems...
It doesn't matter how they differ. The implementation has to make sure my code above works, because that is conforming. Pixie dust is allowed.
MSalters
Thank you for the answer. It's rational.
MSalters, maybe you'll be interested: a generic resetting mixin / function: http://stackoverflow.com/questions/1133738/more-generic-resetable-mixin