c++arduino-esp8266

Freeing memory after using placement new c++


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?


Solution

  • 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.