placement New/delete

Started by
9 comments, last by maya18222 14 years, 10 months ago

#include <iostream>
#include <new>
using namespace std;

class A
{
public:
	A(){ v = 6;}
	~A(){}

	//Placement New
	//===============================================
	static void* operator new(size_t s, void* w){
		return w;
	} 

	//New/Delete
	//===============================================
	static void* operator new(size_t s){
		return ::operator new(s);
	} 
	static void operator delete(void* p){
		delete p;
	}

	//Other New/Delete
	//===============================================
	static void* operator new(size_t s, int i){
		return ::operator new(s);
	} 
	static void operator delete(void* p, int i){
		delete p;
	} 

	int v;
};

int main()
{
	A* p = (A*)malloc(sizeof(A));	//allocate space
	new (p) A;						//placement new
	delete p;						//delete
	
	p = new A;	//call new
	delete p;	//call delete

	p = new(6) A;	//call other new
	delete(6) p;	//call other delete

}


1. How does the placement opertator new call, know to call the constructor? I mean, i just returns the pointer that was passed in. I notice that if i return 0 instead of 'w', the constructor *doesnt* get called. How exactly is this working? 2. Why doesnt the last line "delete(6);" compile? 3. How should i implement and call placement delete? Cheers
Advertisement
operator new is not the same as the new keyword. Poor choice of operator naming on the standards body's part, but understandable due to their want to not introduce extraneous keywords.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Placement delete is called in only one situation: if the constructor of an object throws an exception when allocated with placement new, the corresponding version of placement delete is called. If you want to use a special deallocation method with your object pointer, you'll need to call the destructor manually and then call your deallocation function manually.
Hmmm, thanks, but not really answers to any of my questions. Could you expand a bit. Are you saying that placement delete just calls the destructor manually? such as

this->~();

?
No, placement delete doesn't call the destructor. Placement delete is called in exactly one situation: when the constructor for an object fails by throwing an exception. If the constructor fails, the destructor is not called, because it makes no sense to call the destructor for something that never finished constructing. You cannot call placement delete manually on an object pointer, so no syntax you can think of that would result in placement delete being called will compile.
Sorry, im confused. Could you show me an implementation of placement delete? also, i take it that to trigger it, i could just stick a throw; in the constructor?
throw; is used to re-throw an exception in an exception handler. Using it when you aren't handling an exception is undefined behavior.
Quote:Original post by maya18222
1. How does the placement opertator new call, know to call the constructor?


The operator new doesn't. The expression using the 'new' keyword calls the operator new (in this case, the placement operator new), and then calls the constructor. It knows what constructor to call because it knows the type of the thing being new'd.

Quote:I mean, i just returns the pointer that was passed in. I notice that if i return 0 instead of 'w', the constructor *doesnt* get called. How exactly is this working?


It isn't actually working. The purpose of the operator new overload is to tell the new keyword where the allocated memory is. Ordinarily, placement new does this by just blindly reporting whatever location it was given, since there is no allocation to do. If you return 0 instead, then the constructor gets called through a null pointer, which is undefined behaviour.

Quote:
2. Why doesnt the last line "delete(6);" compile?

3. How should i implement and call placement delete?


Ordinary 'delete' calls a destructor and deallocates memory. Ordinary 'new' allocates memory and calls a constructor. There is no syntax in the language for explicitly calling a constructor upon an existing object (it isn't a member function), but there is for explictly calling a destructor upon an existing object. And when something has been placement new'd, the way to undo the constructor call is with an explicit destructor call.

It is ***not*** OK to use malloc() to declare memory, initialize it with placement new, and then call delete to "call the destructor and deallocate the memory". 'delete' only defined-ly works with memory that was allocated with (an actual allocating form of) new. You must always deallocate memory in a way that corresponds to how it was allocated.

Also, using an 'int' as a "marker" for the "other" new is a little sketchy.

Your example should look more like (if I've actually understood things as well as I think I have):

#include <iostream>#include <new>using namespace std;class marker {} other;class A{public:	A() : v(6) {		// Just for testing!		bool do_throw;		std::cout << "Throw an exception?" << std::endl;		std::cin >> do_throw;		if (do_throw) { throw other; } // evil :)	}	static void operator delete(void* p){		::operator delete(p);	}	// Only gets called if the corresponding 'new' throws an exception.	static void operator delete(void* p, void* w){		// No memory was allocated, so do nothing.	}	// Only gets called if the corresponding 'new' throws an exception.	static void* operator delete(void* p, const marker& i){		std::cerr << "Constructor threw after 'other' new used, so 'other' delete got called" << std::endl;		::operator delete(p);	}	static void* operator new(size_t s, void* w){		return w;	} 	static void* operator new(size_t s){		return ::operator new(s);	} 	static void* operator new(size_t s, const marker& i) {		std::cerr << "zomg, 'other' new used!" << std::endl;		return ::operator new(s);	} 	int v;};int main(){	// Placement new:	try {		A* p = malloc(sizeof(A)); // malloc() doesn't need a cast, and		// you should avoid C-style casts in C++ anyway.		new (p) A;		p::~A(); // explicitly call the destructor		free(p); // deallocate the memory in a way that matches its allocation.	} catch (const marker& exception) {}	try {		// Ordinary new:		p = new A;		delete p;	} catch (const marker& exception) {}	try {		// 'other' new:		p = new(other) A;		delete p; // This is OK: the memory was allocated with ::operator new.	} catch (const marker& exception) {}}


(Edited to cover exceptions and what the others were saying about other forms of delete being called in those situations.
Thanks Zhalman, very helpful, thanks for taking the time :)

But i noticed that i had to correct a number of compilation errors in your code example? Summerised below. Are these just typos?

1. deletes returning void* instead of void
2. p = (A*)malloc(sizeof(A)); ----- Not having the cast wouldnt compile for me
3. Local p defined in try scope
4. p->~A(); rather than p::~A();

Here is my version of your code
#include <iostream>#include <new>using namespace std;class marker {} other;class A{public:	A() : v(6) {		//throw other; // evil :)	}		static void* operator new(size_t s, void* w){		return w;	} 	// Only gets called if the corresponding 'new' throws an exception.	static void operator delete(void* p, void* w){		// No memory was allocated, so do nothing.	}	static void* operator new(size_t s){		return ::operator new(s);	} 	static void operator delete(void* p){		::operator delete(p);	}	static void* operator new(size_t s, const marker& i) {		std::cerr << "zomg, 'other' new used!" << std::endl;		return ::operator new(s);	} 		// Only gets called if the corresponding 'new' throws an exception.	static void operator delete(void* p, const marker& i){		std::cerr << "Constructor threw after 'other' new used, so 'other' delete got called" << std::endl;		::operator delete(p);	}	int v;};int main(){	A* p = 0;	// Placement new:	try {		p = (A*)malloc(sizeof(A)); // malloc() doesn't need a cast, and		// you should avoid C-style casts in C++ anyway.		new (p) A;		p->~A(); // explicitly call the destructor		free(p); // deallocate the memory in a way that matches its allocation.	} 	catch (const marker& exception){		std::cout << "Cought";	}	try {		// Ordinary new:		p = new A;		delete p;	} catch (const marker& exception) {}	try {		// 'other' new:		p = new(other) A;		delete p; // This is OK: the memory was allocated with ::operator new.	} catch (const marker& exception) {}}

Also, i have a few more questions if i may.

Whats the purpose of calling the delete operator if a constructor fails? I take it, its too perform some clean up. But that requires you to know what memory was allocated in the constructor doesnt it? Say you had:-

Constructor(){p = new int();n = new int();m = new int();}


And the delete operator gets called becuase the constructor failed at some point, how would you know which pointer to free/delete? Just assign Null before alloaction, and then check for non null to delete?

I take it that a better method for the above problem would be to just use some form of auto pointer. As i believe, that if a constructor fails, the objects that were constructed inside of the object that failed construction, get their destructors called.

Quote:
Whats the purpose of calling the delete operator if a constructor fails?

Consider:
#include <iostream>class Example{public:    Example() { throw 42; }    void *operator new(size_t amount) {          std::cout << "operator new\n";          return ::operator new(amount);     }    void operator delete(void *ptr) {           std::cout << "operator delete\n";          return ::operator delete(ptr);     }};int main(){    for(int i = 0 ; i < 3 ; ++i )    {        try        {             Example *example = new Example();        }        catch(int)        {        }    }}

Consider what happens if operator delete did not automatically free the memory that was allocated by operator new, in the event the constructor throws an exception. There is no way to recover the pointer to free it later, we would have a memory leak.

In your example (changing "int" to "Foo":
Constructor(){   p = new Foo();   n = new Foo();   m = new Foo();}

If one of these throws (say the last one), then *it* will be cleaned up. The others will not, they leak. This is why we use smart pointers.

This topic is closed to new replies.

Advertisement