I’m working on a medium embedded application in C using OO-like techniques. My “classes” are .h/.c modules using data structs and function pointers structs to emulate encapsulation, polymorphism, and dependency injection.
Now, one would expect a myModule_create(void)
function to come with a myModule_destroy(pointer)
counterpart. But the project being embedded, the resources that are instantiated realistically should never be released.
I mean, if I have 4 UART serial ports and I create 4 UART instances with their required pins and settings, there’s absolutely no reason to ever want to destroy UART #2 at some point during runtime.
So following the YAGNI (You ain’t gonna need it) principle, should I omit the destructors? This seem extremely strange to me but I can’t think of a use for them; resources are freed when the device powers-off.
4
If you don’t provide a way to dispose the object, you are passing a clear message that they have “infinite” lifetime once created. If this makes sense to your application, I say: do it.
Glampert is right; there is no need for destructors here. They would just create code bloat and a pitfall for users (using an object after its destructor is called is undefined behavior).
However, you should be sure that there is really no need to dispose of the objects. For instance, do you need to have an object for a UART that is not currently in use?
The easiest way I have found to detect memory leaks is to be able to cleanly exit your application. Many compilers/environments provide a way to check for memory that is still allocated when your application exits. If one isn’t provided there’s usually a way to add some code right before exiting which can figure it out.
So, I would certainly provide constructors, destructors and shutdown logic even in an embedded system that “theoretically” should never exit for ease of memory leak detection alone. In actuality, memory leak detection is even more important if the application code should never exit.
2
In my development, which makes extensive use of opaque data types to foster an OO-like approach, I too wrestled with this question. At first, I was decidedly in the camp of eliminating the destructor from the YAGNI perspective, as well as the MISRA “dead code” perspective. (I had plenty of resource room, that wasn’t a consideration.)
However… the lack of a destructor can make testing more difficult, as in automated unit/integration testing. Conventionally, each test should support a setup/teardown so that objects can be created, manipulated, then destroyed. They are destroyed in order to assure a clean, untainted starting point for thenext test. To do this, the class needs a destructor.
Therefore, in my experience, the “aint’t” in YAGNI turns out be to an “are” and I ended up creating destructors for every class whether I thought I’d need it or not. Even if I skipped testing, at least a correctly designed destructor exists for the poor slob that follows me will have one. (And, to a much lesser value, it makes the code more reusable in that it can be used in an environment where it would be destroyed.)
While that addresses YAGNI, it does not address dead code. For that, I find that a conditional compile macro something like #define BUILD_FOR_TESTING allows the destructor to be eliminated from the final production build.
Doing it this way: you have destructor for testing/future reuse, and you satisfy the design objectives of YAGNI and “no dead code” rules.
1
You could have a no-op destructor, something like
void noop_destructor(void*) {};
then set the destructor of Uart
perhaps using
#define Uart_destructor noop_destructor
(add the suitable cast if it is needed)
Don’t forget to document. Maybe you want even
#define Uart_destructor abort
Alternatively, special case in the common code calling destructor the case when the destructor pointer function is NULL
to avoid calling it.