tags:

views:

241

answers:

5

I posted earlier (http://stackoverflow.com/questions/1574815/c-oop-in-c-implementation-and-a-bug/1574955#1574955) 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 obj, why is it an
  //incompatible type?
  l->say(*l, "I am a lecturer now");
}

Thanks a lot!

+1  A: 

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

qrdl
Solved it thanks!
nubela
Those leftover warnings are *bad* for health.
Aviral Dasgupta
A: 

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)

Aviral Dasgupta
There are platforms with C compilers but no C++ compilers. Not all code is written for personal computers.
Alan
-1, as it's perfectly valid (and commonplace even before C++) to use C in an OOP fashion. I mostly learnt OOP from studying C code, even if I didn't know what to call it back then.But yes, quite right that the function signatures are incompatible. Probably, the correct answer is to make lecturer begin with a speaker instance: struct Lecturer { struct Speaker parent; ...; }Then it's compatible with a speaker, only longer.
Lee B
No, I was not referring to OOP. I was saying that C's OOP system isn't quite appropriate for inheritance. Besides, that's way too offtopic and it's a debatable matter -- you may like it or you may hate it like hell.
Aviral Dasgupta
A: 

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");
elder_george
A: 

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.
Chris Lutz
A: 

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...

Isak Savo
It's also LGPL, which is a turnoff for some. Let's also not forget the possibility that this is going into an embedded system where we can't afford something as heavyweight as GObject.
Chris Lutz
Fair enough, in an embedded system I agree it may be a bit overweight. Otherwise, using LGPL is pretty safe even for commercial applications from a legal perspective.
Isak Savo