OOP in C, inheritance, and bugs
I posted earlier (OOP in C, implementation and a bug) about my attempt with OOP in C, however as I'm still a new to C, there are a lot of gray areas that are resulting in code issues. I have since tried to implement inheritance, but now I'm getting a new errors, any help here? I've commented in the code below with respect to the warnings I'm getting.
#include <stdio.h>
#include <stdlib.h>
//SPEAKER CLASS
typedef struct speaker {
void (*say)(char *msg);
} speaker;
void say(char *dest) {
//speaker method
printf("%s",dest);
}
speaker* NewSpeaker() {
speaker *s = malloc(sizeof(speaker)); //instantiates a speaker into memory
s->say = say;
return s;
}
//SPEAKER CLASS END
//LECTURER CLASS
typedef struct lecturer {
struct speaker *parent;
void (*say)(struct lecturer *parent,char *msg);
} lecturer;
void lecturer_says(struct lecturer *this,char *msg) {
this->parent->say(msg);
this->parent->say("\nAnd you should be taking notes.\n");
}
lecturer* NewLecturer() {
lecturer *l = malloc(sizeof(lecturer));
l->parent = NewSpeaker();
l->say = lecturer_says;
return l;
}
//LECTURER END
int main() {
speaker *s = NewSpeaker();
s->say("I am a speaker and I can speak.\n");
lecturer *l = NewLecturer();
//compiler gives warning saying i'm giving an imcompatible type, but i'm in fact sending the lecturer开发者_Python百科 obj, why is it an
//incompatible type?
l->say(*l, "I am a lecturer now");
}
Thanks a lot!
NewSpeaker returns pointer to struct speaker
so it should be fine. What exact warning/error message you got from the compiler?
What did you mean by "l->parent should take a pointer instead of an address"? Pointer IS an address.
In l->say()
call you don't need to dereference l
, as it expects pointer
IMHO you'd better to:
- make all methods to receive pointer to context as first arg (i.e. Speaker::say should have signature void say(Speaker* this, char* msg);
Syntax would be a bit uglier but also more general (see below);
- aggregate speaker into lecturer by value, not by reference. This way pointer to lecturer will be safey casted to pointer to speaker.
E.g.:
typedef struct lecturer {
speaker parent;
} lecturer;
- not add new slots for overridden functions but rewrite slots in parent.
- split memory allocation and object initialization.
E.g.:
void* init_speaker(speaker* this) // void* to avoid casts
{
s->say = say;
return this;
}
void* init_lecturer(lecturer* this)
{
init_speaker(this); // we may write init_speaker(this->parent);
this->parent->say = lecturer_say;
}
// usage
speaker* s = init_speaker(malloc(sizeof(speaker)));
speaker* l = init_lecturer(malloc(sizeof(lecturer));
s->say(s, "hello");
l->say(l, "hello");
The problem is here:
l->say(*l, "I am a lecturer now");
Specifically, *l
dereferences your pointer, so you're passing a copy of the struct
rather than a pointer to the struct
. Just do this:
l->say(l, "I am a lecturer now");
No warnings, it works like it's supposed to, everyone is happy.
Also, I want to reiterate my comment, and add a few more things:
say()
's argument shouldn't be calleddest
. That incorrectly implies that data is being written to the location. Why not call itmsg
like you did in your function pointer?say()
's argument should also be aconst char *
rather than achar *
. This is a promise by you to the caller (and enforced by the compiler) that your function will not change their data. Note the prototype to thestrcpy()
function:char *strcpy(char *s1, const char *s2)
. From this, we know the function copies the data ats2
to the locations1
and not the other way around. Why? Becauses2
is aconst char *
, sostrcpy()
can't change it. It's a good promise to make.- This may just be me, but I would always check any pointers for
NULL
before dereferencing them. Check forNULL
after youmalloc()
, and check every important argument to your function forNULL
. You can avoid checking strings forNULL
if you want, because often these will be string literals, but it can't hurt. Some take the approach of "if the caller wants a segfault who am I to stop them?" which is quite fine and dandy if you're going for super efficiency, and some may take the argument of "I want to know early on if we're passing aroundNULL
s without knowing," but I'm a fan of not segfaulting when someone does something stupid.
I suggest you take a look at existing object oriented APIs for C. GObject, part of the GLib package which in turn forms the foundation of the GTK toolkit and GNOME desktop environment is a mature implementation you may be able to use.
You'll get stuff like inheritance, interfaces and events using only portable C. And it won't cost you a dime...
There is no practical reason for you to do this -- ever you should instead use C++. But if you're developing for a platform like the NDS, then it's probably OK to try to do this.
Firstly, once you have declared a structure as a type, there is no need to prefix declarations of that type with struct. Then, as qrdl states, there is no need to dereference l. Thirdly, the NewSpeaker() call gives a warning as they use different ypes -- NewSpeaker returns a "speaker *" and parent is of type "struct speaker *". In the line after that, you're assigning
void lecturer_says(struct lecturer *this,char *msg)
to
void (*say)(struct speaker *parent,char *msg)
so, the types are incompatible. If you want to really, really do such a kind of thing, then you should use a union for it (exchangibly using either speaker or lecturere)
精彩评论