oh oh it's OO - calling encapsulated functions - question for the experts

G

Gibby Koldenhof

Hiya,

I'm setting up some code in the spirit of Design Patterns, OOP, etc. All
nice and well, it handles pretty much all OO style things and serves my
purposes well. There's one last final question remaining: How to properly,
simple and eleganty implement functions. The functions are encapsulated in
the objects, the objects themselves are stored in a binary tree and objects
contain 'methods' (functions) along with data. I don't want to make publicly
available routines since the encapsulation is then gone.

So practically, how do I call a function that is encapsulated in some
structure without using that structure directly (e.g. only through void *)?

The code below are four examples of how to fix this, only example2 is kinda
okay. The biggest problem however is that it is very slow.

If anybody has got an idea of how to do this better (or even different) I'd
love to hear it because I'm kinda stuck with this.

P.s. I don't want to use C++ - it breaks more than fixes in this code base.

This is all in the spirit of stuff like
http://www.planetpdf.com/codecuts/pdfs/ooc.pdf and related techniques and
docs. The GTK system implements something simalar (but in a terrible way
IMHO).

Any tips welcome!

Cheers,
Gibby Koldenhof

/*--------------------------------------------------------------------------
----------*/
/* compile with -ansi -pedantic -Wall */
/*--------------------------------------------------------------------------
----------*/

/* example 1, using func. pointer */

typedef void (*method_t)(void);

extern method_t find_method(const char * name);

void foo1(void)
{
int (*getpixel)(int x, int y) = (int (*)(int,int))find_method("getpixel");

/* this is terrible, we need to cast a heck of a lot for each function
we're gonna call, this clutters up the code and invites trouble if the cast
is wrong */

int pixel = getpixel(1, 2);
}

/*--------------------------------------------------------------------------
----------*/

/* example 2, using var. args */

void do_method(const char * name, ...)
{
/* find method through name and parse arg list */
}

void foo2(void)
{
extern void * new(const char *, ...);

int pixel ;

do_method("getpixel", 1, 2, &pixel);

/* two problems : (1) it is *SLOW*, e.g. parsing arg list and finding
'getpixel' method in the tree take a long time. The latter could be solved
by caching the found pointer.
(2) we can't check var. args on correctness, e.g. if a double should be
passed and the user passes an int it buggers up */

/* possible solution, only pass objects, so we can check the type */

if(1)
{
void * x = new("int", 1);
void * y = new("int", 2);
void * result = new("int", 0);
void * method = new("method::getpixel");

/* this is more elegant but adds tons of indirection, ergo TERRIBLY slow
.... ;-( */

do_method(method, x, y, result);
}
}

/*--------------------------------------------------------------------------
----------*/

/* example 3, using var. args */

typedef struct {
/* table with all possible methods ... ugh */

int (*get_1)(int a);
int (*get_2)(int a, int b);
int (*get_3)(int a, int b, int c);
} methods_t ;

static methods_t methods;

#define some_get(x) methods.get_##x

void foo3(void)
{
/* Yikes, this is terrible! we can only use a real integer (due to
pasting) and the syntax of the thing is terrible */

int pixel = some_get(2)(1,2);
}

/*--------------------------------------------------------------------------
---------*/

/* example 4, using casts and stuff */

typedef struct {
int xsize, ysize, bpp ;
void * data ;
int (*getpixel)(int x, int y);
} image_t ;

void * new(const char * name)
{
static image_t image ;

return &image ;
}

void foo4(void)
{
void * picture = new("image");

/* yuck - there goes the encapsulation, and this is terrible to call -
even if we fix it with a macro */

int pixel = ((image_t *)picture)->getpixel(1,2);
}
 
M

Michael Mair

Gibby said:
Hiya,

I'm setting up some code in the spirit of Design Patterns, OOP, etc. All
nice and well, it handles pretty much all OO style things and serves my
purposes well. There's one last final question remaining: How to properly,
simple and eleganty implement functions. The functions are encapsulated in
the objects, the objects themselves are stored in a binary tree and objects
contain 'methods' (functions) along with data. I don't want to make publicly
available routines since the encapsulation is then gone.

So practically, how do I call a function that is encapsulated in some
structure without using that structure directly (e.g. only through void *)?

The code below are four examples of how to fix this, only example2 is kinda
okay. The biggest problem however is that it is very slow.

If anybody has got an idea of how to do this better (or even different) I'd
love to hear it because I'm kinda stuck with this.

C99:

#define CALL_METHOD(classvar,method,...) \
classvar.method(&classvar, __VA_ARGS__)

where method is a function pointer to be "initialised" by your
"constructor".


C89: Use another calling convention. Something along the lines of
CALL_METHOD_N() where N is the number of additional arguments is
easy to realise but maybe there is a more elegant solution out
there.

As these are macros, they are pretty fast.


Cheers
Michael
 
G

Gibby Koldenhof

C99:

#define CALL_METHOD(classvar,method,...) \
classvar.method(&classvar, __VA_ARGS__)

where method is a function pointer to be "initialised" by your
"constructor".


C89: Use another calling convention. Something along the lines of
CALL_METHOD_N() where N is the number of additional arguments is
easy to realise but maybe there is a more elegant solution out
there.

As these are macros, they are pretty fast.

Cool! I didn't know of this C99 feature (I really need to read up on it) -
using var. args in C89's macro's is a no-no though, but the alternative
(less elegant) way would be to pass an array (or a string for that matter
but then parsing the args back in would take to long) - but assuming I'm
going to compile everything in C99 this still has a problem:

It breaks with the encapsulation, e.g. I can't use generic pointers to
objects ... or can I? (somehow) or maybe I'm missing something?

What I mean is: (compile with gcc test.c --std=c99 --pedantic)

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#define CALL_METHOD(classvar,method,...) \
classvar.method(&classvar, __VA_ARGS__)

typedef struct {
void (*func)(void * class, ...);
} foo_t ;

static void blah(void * class, ...)
{
printf("blah!\n");
}

int main(void)
{
foo_t foo;

foo.func = blah;

CALL_METHOD(foo, func, 1, 2, 3);

/* what I would like but what (of course) doesn't work in this construct :
*/

if(1)
{
extern void * find_class(const char * name);

void * foobar = find_class("something");

CALL_METHOD(foobar, func, 1, 2, 3);
}

return 0;
}

Ergo, the code within the if(1) scope is what I would really like to do so
the definition of foo_t is invisible to the user program.

Thanks, More tips welcome!

Cheers,
Gibby Koldenhof
 
M

Michael Mair

Gibby said:
Cool! I didn't know of this C99 feature (I really need to read up on it) -
using var. args in C89's macro's is a no-no though, but the alternative
(less elegant) way would be to pass an array (or a string for that matter
but then parsing the args back in would take to long) - but assuming I'm
going to compile everything in C99 this still has a problem:

It breaks with the encapsulation, e.g. I can't use generic pointers to
objects ... or can I? (somehow) or maybe I'm missing something?

Nope. However, passing generic pointers effectively does not get you
very far -- and not in a very fast way.
What I mean is: (compile with gcc test.c --std=c99 --pedantic)

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#define CALL_METHOD(classvar,method,...) \
classvar.method(&classvar, __VA_ARGS__)

typedef struct {
void (*func)(void * class, ...);
} foo_t ;

static void blah(void * class, ...)
{
printf("blah!\n");
}

int main(void)
{
foo_t foo;

foo.func = blah;

CALL_METHOD(foo, func, 1, 2, 3);

/* what I would like but what (of course) doesn't work in this construct :
*/

if(1)
{
extern void * find_class(const char * name);

void * foobar = find_class("something");

CALL_METHOD(foobar, func, 1, 2, 3);
}

return 0;
}

Ergo, the code within the if(1) scope is what I would really like to do so
the definition of foo_t is invisible to the user program.

Thanks, More tips welcome!

Hints:
* Use OO. Inheritance helps.

Example:

typedef struct base {
unsigned long control;
Env_Item v;
int (*Construct)(struct base *);
int (*Destruct) (struct base *);
int (*Init) (struct base *, int argc, char **argv);
int (*Execute)(struct base *, int argc, char **argv);
int (*Display)(struct base *);
} Base;

Every derived structure now contains as first member a
variable of type Base. You can store information about the
type in control (bitwise). You are not passing around void *
but base *.
Env_Item belongs to some environment, e.g. an environment
directory, effectively storing the name etc.

The types are registered by either the control entry or
by prefixes and are "created" at the beginning (with storing
the necessary size). When something is to be constructed,
you find out the type by a prefix or suffix (or the complete name).

You can always call Construct, Destruct, Init, Execute and Display
when casting the derived types (ideally: type pointers) to Base(Base *).

e.g.
#define CALL_BAS_METHOD(classvar_p,method,...) \
IS_BASE((Base *)(classvar_p)) ? \
((Base *)(classvar_p))->method((Base *)(classvar_p), __VA_ARGS__) \
: /* Do something sensible */

(untested, just a sketch)

* Make it only as abstract as you need it.
Re-creating C++ with C is worse than using C++ in the first place.

* If you need "private" methods and data, you can hide them in standard
C ways. First idea: One file per class, private stuff hidden in a
"private" * member.
Extension to inheritance left as exercise to the reader. (Ugly)


Cheers
Michael
 
G

Gibby Koldenhof

Nope. However, passing generic pointers effectively does not get you
very far -- and not in a very fast way.



Hints:
* Use OO. Inheritance helps.

Example:

typedef struct base {
unsigned long control;
Env_Item v;
int (*Construct)(struct base *);
int (*Destruct) (struct base *);
int (*Init) (struct base *, int argc, char **argv);
int (*Execute)(struct base *, int argc, char **argv);
int (*Display)(struct base *);
} Base;

Okay, if I understand correctly these are the base methods. Agreed - I
already have those, they include stuff like contruct, destruct, cast, clone,
cout, cin, serialize, etc. Since these routines are generally not
speed-critical I've got them hidden and find them through a string.

I wouldn't mind having one 'Base' type though - but I don't think it helps
me - read below.
Every derived structure now contains as first member a
variable of type Base. You can store information about the
type in control (bitwise). You are not passing around void *
but base *.
Env_Item belongs to some environment, e.g. an environment
directory, effectively storing the name etc.

Okay, but I already do this - my objects are 'simply' encapsulated structs
(a tad more complex than that but anyway) which I can reference through a
void pointer, e.g I can do stuff like:

(just as an example)

void * pic = new("image", 320, 200, 32);

new * blah = clone(pic);

printf("%s\n", typeof(blah)); /* prints 'image' */

delete(pic);

the 'image' object is in turn made up of 'RGB' objects and those are made up
3 doubles. When I call:

void * a = new("int", 2);

call(blah, "divide", a);

it will cascade the call down to the 'double' object and and will figure out
that it will needs to divide all double objects which are contained in RGB
and in turn contained in image. Ergo, I only have to implement a divide
function for the most basic types and inherited types will figure out how to
find it and apply it to themselves. Of course to speed it up it would be
wise to implement the "divide" higher up the chain - but this is later work,
it already works as it is.
The types are registered by either the control entry or
by prefixes and are "created" at the beginning (with storing
the necessary size). When something is to be constructed,
you find out the type by a prefix or suffix (or the complete name).

Agreed, this is what new() also does, you can give it some string and it
will find out what to do, e.g.:

new("int:3", 2, 3, 4)
new("vector", 2.3, 1.2, 4.5);

or even

void * method(void * obj) { return 0; }

void * a = composite(new("int"), new("double"), new("callback", method));

calling the "divide" from the previous example will work on the composite
too - it will divide the int, the double and the result object from method -
at least if that object is dividable.

It's somewhat comparable to templates in C++ in that it doesn't matter what
type it is it will get resolved at run-time, the downside of course (as
opposed to templates) is that it's slower the nice thing is that it's very
dynamic.
You can always call Construct, Destruct, Init, Execute and Display
when casting the derived types (ideally: type pointers) to Base(Base *).

e.g.
#define CALL_BAS_METHOD(classvar_p,method,...) \
IS_BASE((Base *)(classvar_p)) ? \
((Base *)(classvar_p))->method((Base *)(classvar_p), __VA_ARGS__) \
: /* Do something sensible */

Okay, so I can call the basic 'methods' - yet I still cannot call functions
that are not part of the more traditional routines such as constr., destr.
etc. So a getpixel() is very specific to an image object yet I can't put it
in the struct - at least not without throwing away the abstraction .... or
am I missing your point here?

Of course I could make a image_get_pixel(void * obj, int x, int y) which
would only work on an image object but this is not really what I want -
partly because I'm going to duplicate code, and partly because it isn't
contained in the image class.

What would be nicer would be to have a 'read' method, so that:

void * obj1 = call(image, "read", x, y);
void * obj2 = call(volume, "read", x, y, z);

and even

void * file = new("io", "/usr/local/etc/blah.txt", "rb");
void * string = call(file, "read", 12);

would work.
(untested, just a sketch)

* Make it only as abstract as you need it.
Re-creating C++ with C is worse than using C++ in the first place.

True - and maybe I'm going overboard - maybe it's overkill and will bite me
later on because the whole damn system becomes way too slow ...

It's just annoying the hell out of me that the whole system is pretty
simple, straightforward and elegant yet that this last thing seems to be a
real problem.
* If you need "private" methods and data, you can hide them in standard
C ways. First idea: One file per class, private stuff hidden in a
"private" * member.
Extension to inheritance left as exercise to the reader. (Ugly)

What I do is just have a number of fields per method/data - e.g. it can be
protected, private, etc. And indeed each (major) function in a seperate
file. It's just like a nice generic plugin type-thing. The nice thing is
that it can generate all kinds of stuff by itself, like output XML, GUI
code, you can embed scripts into the classes, etc. And finding the classes
is quite fast since they are all in a binary tree that balances itself to
the # of generated objects per class. The big plus of inheritance is that I
can open any file and the system will figure out what type of object it
would need to represent, so an image file will generate an image object, a
MRI dataset will generate a volume file, etc.

It's like C++ only without all the surrounding crap :)

Thanks Michael for the tips!

Cheers,
Gibby Koldenhof
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top