There are several reasons for this.
- Using Opaque Pointers
- Lack of destructors
- Embedded systems (problem)
- Containers
- Inertia
- "Laziness"
Briefly discuss them.
For opaque pointers, it allows you to do something like:
struct CClass_; typedef struct CClass_ CClass;
Thus, the user does not see the definition of struct CClass_ , isolating it from changes to it and allowing other interesting materials, for example, to implement the class differently for different platforms.
Of course, this prohibits the use of CClass stack CClass . But, OTOH, you can see that this does not prohibit the allocation of CClass objects statically (from some pool) - CClass_create is returned CClass_create or perhaps another function of the CClass_create_static type.
Lack of destructors - since the C compiler will not automatically destroy your CClass stack CClass , you need to do this yourself (manually calling the destructor function). Thus, the only advantage is the fact that stack distribution is generally faster than heap distribution. OTOH, you don’t need to use a bunch - you can allocate from a pool or arena or something like that, and it can be almost as fast as stack distribution, without the possible stack distribution problems discussed below.
Embedded Systems - The stack is not an “endless” resource, you know. Of course, for most applications on today's "regular" OS (POSIX, Windows ...) this is almost the case. But on embedded systems, the stack can be only a few KB. These extreme, but even “large” embedded systems have a stack that resides in MB. Thus, it will be exhausted if used too much. When this happens, there is basically no guarantee what will happen - AFAIK, in both C and C ++, that the "Undefined behavior". OTOH, CClass_create() can return a NULL pointer when you have lost memory, and you can handle it.
Containers are C ++ users, such as stack distribution, but if you create the std::vector stack on the stack, its contents will be allocated to the heap. You can configure this, of course, but this is the default behavior, and it makes life easier to say that "all container members are allocated as heaps" rather than trying to figure out how to handle if they are not.
Inertia - well, OO came from SmallTalk. Everything is dynamic there, so the "natural" translation into C is to put everything on a heap. So, the first examples were like that, and they inspired others for years to come.
" Laziness " - if you know that you want only stack objects, you need something like:
CClass CClass_make(); void CClass_deinit(CClass *me);
But if you want to allow both stack and heap, you need to add:
CClass *CClass_create(); void CClass_destroy(CClass *me);
This is more work for the developer, but also confuses the user. You can make slightly different interfaces, but this does not change the fact that you need two sets of functions.
Of course, the reason for "containers" is also partly the reason for "laziness."