0

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?

  • 2
    Every normal `new` should be paired with a `delete`, and every `new[]` with a `delete[]`. There is however no "placement delete", but you should call the destructor of such objects (before you `delete[] memory`). – Some programmer dude Dec 31 '22 at 13:00
  • 4
    You should to do `obj->~display();` to destroy the object, the space can then be reused. This should also be done before freeing the backing memory. Without the manual deletion any resources managed by `class display` will not be released if the backing memory is destroyed first. – Richard Critten Dec 31 '22 at 13:02
  • But should I use the destructor even if I continue to use the object in a kind of loop state in the MCU's code? – Some random guy Dec 31 '22 at 13:16
  • Although I guess I grasped the concept, without destroying the object first I can't release the resources I previously used to create the object – Some random guy Dec 31 '22 at 13:18
  • 1
    The lifetimes are still the same as any other object, you just have to do it all yourself now: 1. Acquire memory. 2. Create object (with placement `new`). 3. Optionally use the object. 4. Destroy object (with manual destructor call). 5. Release memory. Normally some or most of these steps are done by the compiler, placement `new` is for when you need 100% control. – Yksisarvinen Dec 31 '22 at 13:24
  • 1
    `delete` expressions should only ever act on pointers that result from a corresponding `new` expression, otherwise behaviour is undefined. For a placement `new` there is no placement `delete`. `delete *memory` in your case is a diagnosable error, as `*memory` is of type `char`, which is not a pointer. To clean up in your case, first use `obj->~display()` to destroy the object, then the memory can be safely released via `delete [] memory`. – Peter Dec 31 '22 at 13:25
  • 1
    There is only one program running on your device, and it should never exit. And if it exits, your device will need a full reset and reboot. So, no it does not matter one bit. – Michaël Roy Jan 02 '23 at 16:42

1 Answers1

6

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.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • 1
    It is not always necessary to call destructor on objects. If it is trivial or irrelevant the call can be skipped. – ALX23z Dec 31 '22 at 13:40
  • 1
    @ALX23z It is not clear that it is ever strictly required. (The standard only has one sentence about it and it isn't very clearly written.) I already had a paragraph on that, but generalized it a bit. – user17732522 Dec 31 '22 at 13:48
  • Much appreciated man, I actually didn't know what I was asking with this post, I more or less wanted to know if I could delete "memory" while still making use of obj throughout the rest of the program (which in reality is not supposed to end but rather continue looping with the parameters the user is supposed to upload themselves). Also thanks for the other examples, people told me I could use those as well but hadn't tried them out after I used placement new. – Some random guy Dec 31 '22 at 14:37
  • Also, could this be achieved with std::vector? I have access to unique_ptr and optional in Arduino but not in Atmel Studio and wanted to implement this in another project – Some random guy Dec 31 '22 at 14:39
  • 1
    @Somerandomguy You can't delete the memory while you are still using `obj`. `delete[] memory;` will take the object you created with placement-new in it with it. `obj` will be dangling afterwards. `delete[] memory;` will also not call the destructor of `obj`. That needs to happen beforehand (if at all). After `delete[]` it is too late. – user17732522 Dec 31 '22 at 15:38
  • 1
    @Somerandomguy Yes you could use `std::vector obj;` for this, leaving it empty at first and then creating a single object in it which you can refer to with `obj[0]`, but that seems like an even worse misuse than `std::unique_ptr`. If `std::vector` is supported (and it is post-C++11) then `std::unique_ptr` should also be supported. If not, I would recommend just implementing your own. Also note that the `std::unique_ptr` approach is essentially the same as simply a `display *obj = nullptr;` followed by `obj = new display(/*args*/);`, automatically cleaned up with `delete obj;`. – user17732522 Dec 31 '22 at 15:42
  • 1
    @Somerandomguy The point is: The whole placement-new approach is pretty advanced and one can easily get it wrong. It is only required if 1. you can't have any heap allocation during the program execution, 2. you can't make the class (`display`) have an empty state for two-phase initialization and 3. you don't have any library support already implementing the behavior for you (`std::optional` or similar, e.g. `boost::optional`). – user17732522 Dec 31 '22 at 15:44
  • I think I got a better idea now. I merely used placement new because I thought it was the best way to create an object but not have it call its constructor until I had some data from the EEPROM and that I needed the object to be declared as a global variable. The use of a nullptr like you did with display *obj = nullptr turns out to work just as fine for what I wanted to do. The information about when to free allocated memory is also appreciated since I didn't know in what scenario I was supposed to use delete. – Some random guy Dec 31 '22 at 16:23
  • There´s only one thing I still don't get: why if I create a ```display *obj=nullptr``` followed by ```obj = new display(...)``` and then I delete it like ```delete obj``` I'm still able to use functions from the display class? I didn't use the destructor tho which I guess I should, but what is delete doing in that case if I'm still able to use that object? – Some random guy Dec 31 '22 at 16:31
  • 1
    @Somerandomguy `delete obj` calls the destructor of `*obj` for you. Don't call it manually. If you deleted the object, then trying to access it in any way (including calling a member function) causes [_undefined behavior_](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior). This means that you will have no guarantee on how the program will behave. It may crash, it may behave the wrong way, or it may seem to work. In either case that the program is invalid and it may give a different result the next time you try it. – user17732522 Dec 31 '22 at 16:33