Reputation: 127
I had an idea about how to implement interfaces in C. The idea is really simple, but when searching for questions discussing this, I did not see it mentioned.
So, the "standard" inheritance emulation method in C is essentially
struct dad {
arm_t right_arm;
arm_t left_arm;
}
struct kid {
struct dad parent;
diaper_t diaper;
}
Which I think is ugly, because kid.parent.right_arm
just makes less sense than kid.right_arm
. There is another way apparently, if you don't mind using gcc-exclusive flags, which is something like
struct kid {
struct dad;
diaper_t diaper;
}
but this is not portable. What is portable, however, is
// file: dad.interface
arm_t right_arm;
arm_t left_arm;
// file: kid.h
struct kid {
#include dad.interface
diaper_t diaper;
}
For the purposes of this question, I'm not interested in suggestions for alternatives. My question is simply this: what, if anything, is wrong with this approach?
Upvotes: 3
Views: 424
Reputation: 432
You can do it like this:
#define myclass_members int a;\
void (*f)(void)
struct myclass {
myclass_members;
};
#define derived_cls_members myclass_members;\
double d;\
void (*g)(void)
struct derived_cls {
derived_cls_members;
};
If you are interested, you can check out a whole framework based on this here.
Edit: turns out this approach may work, but as others have pointed out it's dubious. A better option would be to use the anonymous struct expansion added in c11, which can still be used with the preprocessor as @tgregory pointed out.
#define myclass_members struct { int a; void (*f)(void); }
Edit 2: A further look into this reveals that the first approach (with only the preprocessor) should practically work. So if you are not concerned about the mentioned in the comment assignment issues, or the issues that may come up with using functions like memset()/memcpy(), it should be fine.
Sources:
Are C-structs with the same members types guaranteed to have the same layout in memory?
What is the strict aliasing rule?
Edit 3: To not get something optimized away, you'd want to compile with the -fno-strict-aliasing flag if compiling with optimization.
Bottom line: it's dirty, it's a hack, but should work in practice.
Upvotes: 2
Reputation: 180978
Since you pose the question in the context of inheritance, I suppose a more complete (and correct) example would be this:
arm_t right_arm;
arm_t left_arm;
struct dad {
#include "dad.interface"
};
struct kid {
#include "dad.interface"
diaper_t diaper;
};
There are several problems with this, among them:
It's not really inheritance -- it's merely two structures that happen to have matching initial members. struct kid
has no other relationship with struct dad
, and the former certainly does not inherit anything from the latter. From struct kid
s perspective, it's not even clear that a struct dad
exists.
Although you did not use the term, it is common (albeit unwarranted) for people to assume that polymorphism goes hand-in-hand with inheritance. You don't get that with your approach. Despite sharing a common initial sequence of members, the two structure types are incompatible, and pointers to those types are incompatible, as that term is used by the standard. In particular, accessing a struct kid
via a pointer of type struct dad *
violates the strict aliasing rule, and therefore produces undefined behavior. (The same does not apply when struct kid
embeds a struct dad
as its first member.)
The approach has poor extensibility. For example, suppose you want to add struct son
and struct daughter
inheriting from struct kid
. Now you have to pull out struct kid
's members into its own separate header, too. I guess you could do that preemptively if you can predict which structure types anyone might ever want to inherit from, or if you just do it to every single structure type. Yikes, what a mess.
Upvotes: 3