开发者

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 called dest. That incorrectly implies that data is being written to the location. Why not call it msg like you did in your function pointer?
  • say()'s argument should also be a const char * rather than a char *. 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 the strcpy() function: char *strcpy(char *s1, const char *s2). From this, we know the function copies the data at s2 to the location s1 and not the other way around. Why? Because s2 is a const char *, so strcpy() 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 for NULL after you malloc(), and check every important argument to your function for NULL. You can avoid checking strings for NULL 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 around NULLs 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)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜