I was trying to solve a problem that I ran into while programming the ESP8266 MCU, I basically needed to declare an object as global so that it wouldn't cause issues with the callbacks that one of the libraries does when the user sends an HTTP request, but I also needed to wait until I had some data from the EEPROM before calling the constructor so a lot of people told me that I should use placement new which worked perfectly for what I was trying to do. There's still something I don't understand though: after I have called the constructor for the object since it was declared globally and I'm trying to keep it around I shouldn't delete it by calling the destructor, but should I delete the first pointer that I used to save the object (I'm not entirely sure if I'm wording this right)?
class display{
public:
display(int b){
std::cout<<"the value of a: "<<b;
}
};
char *memory= new char[sizeof(display)];
display *obj;
int main(){
int a=69;
obj=new(memory) display(a);
return 0;
}
That is more or less what I did in the code for the ESP (without all the other stuff, but it is the same in terms of what I tried to do with placement new). My question is after someone does something like that, would it cause issues if I were to delete *memory or is it not necessary?
There is no need for the allocating new
here. You just need to make sure that you have an array of sufficient size and alignment:
alignas(display) std::byte memory[sizeof(display)];
display *obj = nullptr;
(Instead of std::byte
you can use unsigned char
, but I think std::byte
, which is available since C++17, expresses the intent as raw memory storage better.)
Then construct the object with
obj = new(memory) display(a);
and when it is not needed anymore, call its destructor explicitly:
obj->~display();
No delete
is needed in this case. With your approach an additional delete[] memory;
after the destructor call would be required to free the memory allocated with the first new
, if you don't intent to reuse it after the destructor call (which you can do e.g. in a loop constructing a new display
with placement-new). Note that you need to call the destructor on obj
and the delete[]
on memory
. This is not interchangeable. memory
is a pointer to the allocated memory block and obj
a pointer to the object nested in it. The former was allocated with allocating new[]
, so required delete[]
, and the latter was only created with the (non-allocating) placement-new, so requires only an explicit destructor call.
Of course, you can consider whether the destructor call is really needed. If the display
doesn't hold any resources that need to be cleaned up, then you can skip it, although I would be safe and call it anyway, just in case display
will be changed later.
Also, the standard library since C++17 implements all of this as std::optional
. If you can use it, then do so:
std::optional<display> obj;
// obj is now empty, can be tested with `if(obj)`
obj.emplace(/* constructor arguments */);
// obj now contains a display that can be accessed like a pointer with * and ->
// destructor of obj will take care of correctly destroying the display
emplace
can also be called multiple times to replace the display
with a new one (and calling the old one's destructor) or .reset()
can be used to explicitly empty the optional
.
If you don't have C++17 available, a std::unique_ptr
can be used in a similar way, except that it will use a heap allcoation, which std::optional
doesn't, and that std::unique_ptr
isn't copyable even if display
is, while std::optional
will be.
std::optional<display> obj;
// obj is now empty, can be tested with `if(obj)`
obj = std::make_unique<display>(/* constructor arguments */);
// obj now contains a display that can be accessed like a pointer with * and ->
// destructor of obj will take care of correctly destroying the display
obj
can be reassigned in that way multiple times as well or reset with = nullptr;
or .reset()
and in either case it will take care of correctly destroying any display
just like std::optional
does.