5

I'm working on an embedded system, where memory is scarce, and more importantly, since there's a soft realtime constraint (i.e. it's a bug if we don't make the deadline, but no one dies), I cannot use dynamic memory allocation.

However, there's the occasional need to re-initialize a subsystem, and there's been a number of bugs related to not-quite-everything getting either cleaned up or reset correctly. This is of course exactly the problem that constructors and destructors are supposed to solve, but since we don't allocate dynamically, we can't use the idiom where we'd destruct the object and then allocate a new one from scratch (mostly the objects of relevance here are globals).

So in the end there's usually setup code in a constructor, and a reinitialize -type function, which resemble each other but are not identical, since the reinitialize function also does a lot of what a destructor would do.

One way out that I'm thinking of is to write a "renew" -template (this is just a draft, may contain erors and possibly not complet):

template<typename T, typename .. Args>
void renew(T & obj, Args&&... args) {
  obj.~T();
  new(&obj) T(std::forward<Args>(args)...); 
}

which could be used to reinitialize non-dynamically alloctated variables. So for example

A a{17};
... //Do something with a
renew(a, 14);
...//work with the new a, no need to reallocate memory

This would allow getting some of the advantages of constructors and destructors (primarily single way to initialize and deinitialize an object) without dynamic memory allocation. Note that the usage above is simplified, in practice this would mostly be used in very specific points in the main loop and on global objects representing the actual physical subsystems.

Question: is this a sensible way of going about it? Or is there a better alternative?

There's a very similar question here Calling a constructor to re-initialize object. I'm asking much the same thing but specifically in context of embedded programming, where I can't do things the usual way with dynamic allocation.

Timo
  • 739
  • 1
  • 6
  • 13
  • Then the OOP doesn't really suit you, because after `obj.~T();` the device should like, stop existing, puff, dissapear. I've seen most singleton, hardware representing objects have almost empty constructor, the copy constructors deleted, and some `.init()` and `.reset()` and `.deinit()` member functions and usually lazy written buggy, never called destruction. You can't "destruct" a device (you can set it to sleep mode). If `A` would represent "device state", then you could copy it, then implement copy constructor and later do `device.apply(device_state)` where "device" represents the device. – KamilCuk May 29 '19 at 07:34
  • @KamilCuk Of course the destructor can't literally destroy the device (hopefully!), but if you want to make the analogy stick, let's say that the destructor destroys the controlling interface to the object, so that we can build a new interface with new parameters. – Timo May 29 '19 at 07:43
  • Let your object go out of scope and re-enter the scope. Manually calling a destructor on a stack variable is almost surely an error. – Passer By May 29 '19 at 09:14
  • @PasserBy ok, you're right about the stack variable case, the example isn't really good (I'll try to find time to write a bit more thorough one with context). However, the really relevant case is a global variable representing a peripheral, and I want to reinitialize both the hardware and the corresponding object. That is, the destructor shuts the hardware down in a controlled manner, and the constructor then resets the hardware and initializes the objects internal state to correspond to a freshly restarted hardware. – Timo May 29 '19 at 09:24
  • You still have a design flaw in that case. Why do you have a global object that is supposed to be repeatedly destroyed and reconstructed? You are deliberately avoiding automatic constructors/destructors so that you can call them manually. – Passer By May 29 '19 at 09:33

3 Answers3

3

Dynamic memory allocation and constructors and destructors are completely unrelated.

If you are using new or delete (like in your renew function), you are using dynamic memory allocation.

A constructor or destructor does not always mean you are dynamically allocating memory.

The 'renew' function should probably be implemented in the operator= of the class, not as an external function, so:

A a{17}
...
a = 14;
...

Example of what you should do:

class TwoInts
{
private:
    int int1;
    int int2;
public:
    TwoInts(int a = 6, int b = 7): int1(a), int2(b) {}
    TwoInts(const TwoInts& other): int1(other.int1), int2(other.int2) {}

    TwoInts& operator=(TwoInts& other)
    {
        a = other.a;
        b = other.b;
    }
};

TwoInts i(16);
//do stuff
i = TwoInts(68, 14);
//do stuff

The above code does not do any memory allocation.

Sabrina Jewson
  • 1,478
  • 1
  • 10
  • 17
  • Note that I'm using placement new, which specifically doesn't do any memory allocation. And my point here is specifically that constructors and destructors need not be related to memory allocation. – Timo May 29 '19 at 07:20
  • Sorry, I got confused and answered in the wrong way. I fixed it now I think – Sabrina Jewson May 29 '19 at 07:22
  • My point is a bit different here, of course for a class which is just a data container I can just assign. But a better example here is something like a DAC peripheral, which needs to be setup via a number of registers in a very specific way. Or some other peripheral where the class constructor doesn't necessarily even take any parameters, but the important thing is that the hardware device gets reset and the internal state of the managing class gets updated to reflect that. – Timo May 29 '19 at 07:26
1

The only reason I see were you would like to your the construction you propose is where the new class would be of a different derived type. But in this case you would have to be really be careful to use the exact same amount of memory, not to override following variables. I know of one example where this is used in mbed. It used to retype their function callbacks, search for "new" and you will various examples.

I am wondering if you have considered using a "clear" function in your class? In my honest opinion as far as I can see now this would be the easiest solution.

An alternative solution would be to write a memory manager which has a fixed buffer size. From this buffer you can then "dynamically" allocate memory. I do not think this is the solution you are looking for as this is more useful for when you often need to allocate and deallocate multiple objects.

Bart
  • 1,405
  • 6
  • 32
  • Various variations of "clear" is exactly what I have, and the problem is keeping the constructor (which sets everything up the first time) and "clear" in sync, so that both the state of the object and the hardware peripheral is exactly the same as after a system reset. Of course I could simply not set anything up in the constructor, and have a initialize/deinitialize pair, but then I'll have objects in a non-usable state after construction, everybody needs to remember to call the constructors etc. – Timo May 29 '19 at 08:08
  • Yes that is comment. If clear is not virtual you could call it from the constructor instead of implementing "similar" code. If you fear people not calling the correct functions maybe consider making a framework were every body derives from one base class which has the init, reset and deinit functions pure virtual. Everybody needs to implement them in that case. Next you have just one manager class which calls all the correct functions. You just have to implement the sequence once. – Bart May 29 '19 at 08:14
  • Okay, seems possible in principle, but that seems a lot like reinventing the mechanics behind the compilers invocation of constructors/destructors in the first place, at the cost of making all classes a member of the same hierarchy. I don't really see the benefit vs "renew". – Timo May 29 '19 at 08:49
  • Under the assumption that the same amount of memory is used technically nothing. I think it is more of a style thing. – Bart May 29 '19 at 09:42
0

I cannot use dynamic memory allocation

Then the keywords new and delete cannot be allowed to exist in your source. Also make sure to nuke the .heap segment from your memory map.

Similarly, you don't need/want RAII for any class that's some manner of driver. In buzz word terms, any such class must be a "singleton". Or rather, a class that allows for n number of static instances, where n is the number of hardware devices of that type present.

As for the solution to your problem, it is simply this:

public:
  void construct() { ... }

  Foo()
  : /* init internal stuff here if needed */
  {
    /* init internal stuff here if needed */    
    construct();
  }

Where the constructor ensures that everything has default values, and construct is written so that it either works on non-initialized internals (null etc), or in case the internals are set, free up resources.

But there should not even be anything for you to "free up", since this is an embedded system. If you need to gracefully terminate on-going data transmissions, NVM writes etc, that code shouldn't be in the destructor. Because you shouldn't need a destructor (nor should you need the rest of the rule of three), since you should only have one instance of the class.

Lundin
  • 195,001
  • 40
  • 254
  • 396