Ok, I'll try one last time, this time, keeping to the question.
I'll demonstrate below that nothing is as simple as asking for one feature only, and that with that feature comes others, implicitly.
Note: The code below should be considered as pseudo-code. It won't (obviously) compile if processed by a C compiler, and is written only to illustrate some points.
I Want RAII
This is pretty straightforward, already asked before this post, and nothing spectacular or outrageous, at first glance.
This means that when I acquire resources, and I want them freed in a deterministic way.
In C, we would then need to use structs for that, as they are the only true types the user can define.
struct MyStruct
{
HANDLE myResource ; /* HANDLE is some type of resource */
} ;
For that, I need to code a destructor for MyStruct, because we assume HANDLE is not RAII powered.
For RAII, we need a Destructor?
But how to code a destructor in C?
There are several ways. Some will be C code, others will need extension to the language.
Destructor as an external function?
We could try a function inside the structure, but this is not C. So we'll try instead an external function.
For example:
struct MyStruct
{
HANDLE myResource ;
} ;
void finalize(MyStruct * myStruct)
{ deallocateSomeResource(myStruct->myResource) ; }
But the name can't be finalize. Unless we can use function overloading in C (which we can't).
The other solution would be to add the name of the class for the finalizer:
void finalize_MyStruct(MyStruct * myStruct)
{ deallocateSomeResource(myStruct->myResource) ; }
Now, this would work... But what if we have collision of symbols?
I mean, perhaps I already have a structure called finalize_MyStruct !!!
Forbid use of prefix "finalize_"?
Why not. After all, if in C++, the standard forbids the use of symbols prefixed by "_", then we could change the C standard to forbid any symbol prefixed by "finalize_".
Still, this would perhaps break some valid code...
Using the name of the struct?
Another solution would be to re-use the name of the struct:
struct MyStruct
{
HANDLE myResource ;
} ;
void ~MyStruct(MyStruct * myStruct)
{ deallocateSomeResource(myStruct->myResource) ; }
So we would need an extention of C to accept a function with the same name of a struct, prefixed by a "~", which would be considered as the destructor of the struct.
We could add a little syntactic sugar, like the this keyword, and put the function inside the struct, but this is not mandatory. Isn't it?
Now, this would work: Now, with the extension, C can destroy any MyStruct allocated on the stack:
void doSomething()
{
MyStruct oMyStruct ;
oMyStruct.myResource = allocateSomeResource() ;
/* etc. */
} /* At the end of the scope, have the compiler call ~MyStruct(&oMyStruct) */
And what if the headers are broken?
This could happen:
/* HeaderA.h */
struct MyStruct
{
HANDLE myResource ;
} ;
/* HeaderB.h */
void ~MyStruct(MyStruct * myStruct) ;
/* SourceB.cpp */
void ~MyStruct(MyStruct * myStruct)
{ deallocateSomeResource(myStruct->myResource) ; }
/* SourceOne.h */
#include "HeaderA.h"
void doSomething()
{
MyStruct oMyStruct ;
oMyStruct.myResource = allocateSomeResource() ;
/* etc. */
} /* At the end of the scope, nothing happens as no destructor visible */
/* SourceTwo.h */
#include "HeaderA.h"
#include "HeaderB.h"
void doSomethingElse()
{
MyStruct oMyStruct ;
oMyStruct.myResource = allocateSomeResource() ;
/* etc. */
} /* At the end of the scope, call ~MyStruct(&oMyStruct) */
In doSomething(), as no destructor declaration was visible, the object will leak. In doSomethingElse(), everything is ok.
So it means we must tie together MyStruct and its destructor... By putting it in the struct?
Destructor as an internal function?
We can add another extension to the C language to tie the struct and its destructor by putting them together:
struct MyStruct
{
HANDLE myResource ;
void ~MyStruct(MyStruct * myStruct)
{ deallocateSomeResource(myStruct->myResource) ; }
} ;
Again, the this keyword would have been cool, but not necessary.
So, we can know have RAII, isn't it?
No.
Because this code would crash:
void doSomething()
{
MyStruct oMyStruct ;
} // At the end of the scope, call ~MyStruct(&oMyStruct)
Because oMyStruct was not correctly initialized, and thus, oMyStruct.myResource has an invalid value.
So it means we need an... Initializer!!!
For RAII, we need a Constructor?
We'll follow the convention adopted above. This means the following code:
struct MyStruct
{
HANDLE myResource ;
void MyStruct(MyStruct * myStruct)
{ allocateSomeResource(myStruct->myResource) ; }
void ~MyStruct(MyStruct * myStruct)
{ deallocateSomeResource(myStruct->myResource) ; }
} ;
Cool. Now, RAII works, doesn't it?
Well, not quite:
void doSomething()
{
/* create one pMyStruct on the heap */
MyStruct * pMyStruct = malloc(sizeof(MyStruct));
/* Etc. */
free(pMyStruct) ;
}
Malloc will only return free memory. The user must initialize it by hand. And free will only free the memory. The user must finalize it by hand...
So we need... new and delete !!!
For RAII, we need a new and delete?
The first idea would be to add functions inside the struct, but this would be quite tedious. So we will use C macros!!!!
#define NEW(type, pointer) \
do \
{ \
pointer = malloc(sizeof(type)) ; \
type(pointer) ; \
} \
while(false)
#define DELETE(type, pointer) \
do \
{ \
~type(pointer) ; \
pointer = free(pointer) ; \
} \
while(false)
But of course, the compiler could generate at compile time the code, instead or relying on macros. But the idea is here..
Cool. Now, RAII works, doesn't it?
Well, not quite, as seen in the following code:
void doSomething()
{
/* create 25 pMyStruct on the heap */
MyStruct * pMyStruct = malloc(sizeof(MyStruct) * 25);
/* Etc. */
free(pMyStruct) ;
}
For RAII, we need a new[] and delete[], too?
Same problem than with new and delete.
Same solution.
So I guess that everything's Ok, no? For example, the following code:
void doSomething(MyStruct p_oMyStruct)
{
/* Etc. */
p_oMyStruct = getAnotherStruct() ;
/* Etc. */
}
Oops, no...
For RAII, we need a copy-constructor and an assignment function?
struct MyStruct
{
HANDLE myResource ;
void MyStruct(MyStruct * myStruct)
{ allocateSomeResource(myStruct->myResource) ; }
void MyStruct(MyStruct * myStruct, MyStruct * myCopy)
{ allocateSomeResourceByCopy(myStruct->myResource, myCopy->myResource) ; }
void operator = (MyStruct * myStruct, MyStruct * myCopy)
{
deallocateSomeResource(myStruct->myResource) ;
allocateSomeResourceByCopy(myStruct->myResource, myCopy->myResource) ;
}
void ~MyStruct(MyStruct * myStruct)
{ deallocateSomeResource(myStruct->myResource) ; }
} ;
I won't elaborate on the fact that we have function overloading, again. This time, between the default constructor and the copy constructor. This means three cases, all in all, of function overloading. This can be handled silently by the compiler (indeed, C does a little of overloading on its own), so I won't mention it too much.
Conclusion?
Just mention one C++ feature you like, and you'll see most will come attached to it.
Conclusion, for adding RAII to C, we would need to add to C the following extensions, with a syntax similar to C++:
- A Destructor
- A Constructor
- struct methods inside struct
- new/delete
- new[]/delete[]
- Copy Constructor
- operator = Overloading
And some syntactic bonus would be interesting:
- this keyword
- function overloading
And, all in all, I believe there are still some things I forgot, needing more C++ features.