Too much encapsulation?

C

copx

I have recently read "Everything you ever wanted to know about C types" by
Peter Seebach (1), and learned about incomplete types. Now, I realise the
value of encapsulation, but I wonder whether it is proper C style to use
that much of it. By using incomplete types you end up with something like
this:

object_set_colour(object, RED);
colour = object_get_colour(object);

instead of:

object->colour = RED;
colour = object->colour;

If the type of "object" is incomplete, you could not write object->colour or
something like that, because accessing data of incomplete type is not
possible. Only the file which contains the get/set rountines defines the
type. You end up with something that looks very much like OOP + a terrible
overhead. Keep in mind that you need to keep the get/set functions + the
actual type definition in a seperate file to get the encapsulation effect.
That means that no mainstream C compiler will be able to optimize those
function calls away (AFAIK neither GNU, nor MS, nor Borland can inline
functions from other files).

So do you consider that proper C style?
Personally, I am not sure what to think of this. It seems to scream "Why do
you not use C++ already?!".

Link:
http://www-128.ibm.com/developerwor...y=C+types&Search.x=0&Search.y=0&Search=Search
 
U

user923005

I have recently read "Everything you ever wanted to know about C types" by
Peter Seebach (1), and learned about incomplete types. Now, I realise the
value of encapsulation, but I wonder whether it is proper C style to use
that much of it. By using incomplete types you end up with something like
this:

object_set_colour(object, RED);
colour = object_get_colour(object);

instead of:

object->colour = RED;
colour = object->colour;

If the type of "object" is incomplete, you could not write object->colour or
something like that, because accessing data of incomplete type is not
possible. Only the file which contains the get/set rountines defines the
type. You end up with something that looks very much like OOP + a terrible
overhead. Keep in mind that you need to keep the get/set functions + the
actual type definition in a seperate file to get the encapsulation effect.
That means that no mainstream C compiler will be able to optimize those
function calls away (AFAIK neither GNU, nor MS, nor Borland can inline
functions from other files).

MS VC++ can do it.
And if you do the cheesy, sleezy include trick all of them can do it:

/* main.c starts here... */
#include "file1.c"
#include "file2.c"
#include "file3.c"
#include "file4.c"
#include "file5.c"
int main(void)
{
return some_important_function();
}
/* main.c ends here... */
So do you consider that proper C style?
Personally, I am not sure what to think of this. It seems to scream "Why do
you not use C++ already?!".

Link:http://www-128.ibm.com/developerworks/search/searchResults.jsp?search...

If I want C++, then I code in C++.

If I want to write a Unix style filter, I will use C.

On the other hand, I have no problem reading or even enjoying C code
written in a C++ style.

On the other, other hand, I have seen programs where everything is
typedef-ed into some abstract type (even things like loop indexes) and
find those programs to be a lot less transparent to understand than
one more simply written.

IMO-YMMV.
 
B

Ben Bacarisse

copx said:
I have recently read "Everything you ever wanted to know about C types" by
Peter Seebach (1), and learned about incomplete types. Now, I realise the
value of encapsulation, but I wonder whether it is proper C style to use
that much of it. By using incomplete types you end up with something like
this:

object_set_colour(object, RED);
colour = object_get_colour(object);

If you end up with this you don't really *have* any encapsulation.
All you've done invent new syntax for:
instead of:

object->colour = RED;
colour = object->colour;

as you have spotted.

Incomplete types are at their most useful when the interface you have
to define is small compared to the complexity (or probable
variability) of the implementation that is hidden behind it.
 
I

Ian Collins

copx said:
I have recently read "Everything you ever wanted to know about C types" by
Peter Seebach (1), and learned about incomplete types. Now, I realise the
value of encapsulation, but I wonder whether it is proper C style to use
that much of it. By using incomplete types you end up with something like
this:

object_set_colour(object, RED);
colour = object_get_colour(object);

instead of:

object->colour = RED;
colour = object->colour;

If the type of "object" is incomplete, you could not write object->colour or
something like that, because accessing data of incomplete type is not
possible. Only the file which contains the get/set rountines defines the
type. You end up with something that looks very much like OOP + a terrible
overhead. Keep in mind that you need to keep the get/set functions + the
actual type definition in a seperate file to get the encapsulation effect.
That means that no mainstream C compiler will be able to optimize those
function calls away (AFAIK neither GNU, nor MS, nor Borland can inline
functions from other files).

So do you consider that proper C style?

Yes, and it's a very common idiom (have you ever manipulated a FILE
object?). Sometimes you want to use encapsulation to present a
consistent interface over a number of platforms. Other times you may
wish to provide a stable interface to an object that is liable to change
between releases of you application.
Personally, I am not sure what to think of this. It seems to scream "Why do
you not use C++ already?!".
Nothing to do with C++, unless you are using a similar idiom with opaque
types, C++ classes lay their innards bare for all to see.
 
S

Stephen Sprunk

copx said:
I have recently read "Everything you ever wanted to know about C types" by
Peter Seebach (1), and learned about incomplete types. Now, I realise the
value of encapsulation, but I wonder whether it is proper C style to use
that much of it. By using incomplete types you end up with something like
this:

object_set_colour(object, RED);
colour = object_get_colour(object);

instead of:

object->colour = RED;
colour = object->colour;

If the type of "object" is incomplete, you could not write object->colour
or something like that, because accessing data of incomplete type is not
possible. Only the file which contains the get/set rountines defines the
type.

That's one way to do it, in particular if you're writing a library to be
used by others and there's a substantial risk that versions will get out of
sync (for instance, you change your library code but they don't recompile
their application).

The other way to do it is to define function-like macros or static inline
functions in the header file that the application includes, which allows the
compiler to optimize away the encapsulation. This is safe as long as you
trust coders using your interface not to peek at the implementation and both
sets of code will always be recompiled if you change the internals.
You end up with something that looks very much like OOP + a terrible
overhead.

There is no guarantee it will perform worse if you do end up with a
function-call interface. Sure, it's likely, but (a) compilers continually
get better at optimizing, (b) CPUs get better every year at executing bad
code, and (c) you shouldn't worry about optimization until your program is
correct and you can prove there's a performance problem that merits writing
less-maintainable code.
Keep in mind that you need to keep the get/set functions + the actual type
definition in a seperate file to get the encapsulation effect.
That means that no mainstream C compiler will be able to optimize those
function calls away

That doesn't mean they need to be in a separate .c file; see above.
(AFAIK neither GNU, nor MS, nor Borland can inline functions from other
files).

There do exist compilers that can optimize, including inlining, across
translation units. I don't know the status of the ones you list; ask Google
about "Whole-Program Optimization". My understanding is that's fairly rare
today; at best you get inter-procedural optimizations within a single
translation unit with most compilers. That argues for (today) sticking with
the latter encapsulation strategy I describe above, rather than the one
you're assuming, if you can show the latter causes performance problems.

S
 
E

Eric Sosman

copx said:
I have recently read "Everything you ever wanted to know about C types" by
Peter Seebach (1), and learned about incomplete types. Now, I realise the
value of encapsulation, but I wonder whether it is proper C style to use
that much of it. By using incomplete types you end up with something like
this:

object_set_colour(object, RED);
colour = object_get_colour(object);

instead of:

object->colour = RED;
colour = object->colour;

If the type of "object" is incomplete, you could not write object->colour or
something like that, because accessing data of incomplete type is not
possible. Only the file which contains the get/set rountines defines the
type. You end up with something that looks very much like OOP + a terrible
overhead. Keep in mind that you need to keep the get/set functions + the
actual type definition in a seperate file to get the encapsulation effect.
That means that no mainstream C compiler will be able to optimize those
function calls away (AFAIK neither GNU, nor MS, nor Borland can inline
functions from other files).

So do you consider that proper C style?

"Proper" is a judgmental term I won't try to address.
As for the pattern, yes: It's a perfectly good and useful
way to use C. Let's modify your example a little bit:

colour_set_rgb(&hue, RED, 1.0); /* RED to 100% */
cyan = colour_get_cmy(&hue, CYAN); /* get CYAN value */

That is, suppose there is a colour object that supports several
different perceptual color models: RGB, CMY, CMYK, CIE, ... You
can set the components of any model, and read back the components
of any other model: the object takes care of the transformations.
You can even do mixed-mode operations:

colour_set_rgb(&hue, RED, 1.0); /* red to the max */
colour_set_hsv(&hue, VAL, 0.2); /* ... but make it dim */
black = colour_get_cmyk(&hue, BLACK); /* how much black? */

Now: How are you going to accomplish this with the struct->element
approach? By using mutators and accessors, you give yourself an
opportunity to do some processing at interesting moments, to
defend against bogus settings (colour_set_rgb(&hue, RED, -42e9)),
and in general to change the internal representation of the object.

This freedom isn't always important, and it's silly to be a
slave to encapsulation orthodoxy for no purpose. But it can also
be a potent tool, capable of simplifying designs and easing
debugging. The trick is to know when to indulge and when to
abstain.
Personally, I am not sure what to think of this. It seems to scream "Why do
you not use C++ already?!".

Because you'll go straight to Hell, of course. Where your
body, like your name, will be mangled.
 
R

Roland Pibinger

copx said:
If the type of "object" is incomplete, you could not write object->colour or
something like that, because accessing data of incomplete type is not
possible. [...]
So do you consider that proper C style?

Yes, and it's a very common idiom (have you ever manipulated a FILE
object?).

Unfortunately you frequently see generous 'un-encapsulation' in C
code. Structs are defined completely in the header file but only
pointers to functions are used in the function declarations (other
'private' elements (defines, macros, enums) are also unnecessarily
placed into the header). Many programmers seem to be unaware of gratis
encapsulation.
Nothing to do with C++, unless you are using a similar idiom with opaque
types, C++ classes lay their innards bare for all to see.

but not for all to access. Encapsulation is independent of language
and paradigm (it's not an original OO feature).
 
C

copx

[snipping example of sensible use]
This freedom isn't always important, and it's silly to be a
slave to encapsulation orthodoxy for no purpose. But it can also
be a potent tool, capable of simplifying designs and easing
debugging. The trick is to know when to indulge and when to
abstain.

Yes, I think that is probably the way to go.

[snip]
 
A

Al Balmer

If you end up with this you don't really *have* any encapsulation.
All you've done invent new syntax for:


as you have spotted.

Not quite. With the first variation, you don't need access to the
components of object, or even to know their names or other properties.
 
B

Ben Bacarisse

Al Balmer said:
Not quite. With the first variation, you don't need access to the
components of object, or even to know their names or other
properties.

Good point. And you also have a place to put checks and debug tests,
which can be quite handy. But you are still not getting the most from
the *idea* of encapsulation if this is what most of the interface
looks like.
 
A

Al Balmer

Good point. And you also have a place to put checks and debug tests,
which can be quite handy. But you are still not getting the most from
the *idea* of encapsulation if this is what most of the interface
looks like.

Right. The "get/set" routines are often needed, but hopefully there
are higher-level aspects to the interface.
 
M

Malcolm McLean

Roland Pibinger said:
Unfortunately you frequently see generous 'un-encapsulation' in C
code. Structs are defined completely in the header file but only
pointers to functions are used in the function declarations (other
'private' elements (defines, macros, enums) are also unnecessarily
placed into the header). Many programmers seem to be unaware of gratis
encapsulation.
Virtually all the code on my website is written in the FILE * paradigm form.
I don't like the term "object" because it gets confused with "object
oriented", which my paradigm isn't. However I always have a constructor, a
destructor, and functions which operate on the object.

However I always expose the structure in the header. That is so that people
can bypass the encapsulation easily. For instance whilst debugging it is
often handy to be able to print out or set a field directly without touching
the file that contains the member functions.
but not for all to access. Encapsulation is independent of language
and paradigm (it's not an original OO feature).
There is a big problem with syntax.

/* fine */
getpixel(image, x, y, &red, &green, &blue);
luminance = red * 0.3 + green * 0.6 + blue * 0.1;

/* unreadable */
Color col = object.getcolour( cursor.getx(), cursor.gety() );
object.set_Luminance( col.getrgbchannel(Color::RED) * 0.3 +
col.getrgbchannel(Color::GREEN) * 0.6 + col.getrgbchannel(Color::BLUE) *
0.1)

The second is what object type code quickly ends up looking like. There's
nothing difficult going on. It purely the fact that the symbols are nested
too deeply.
 
B

Ben Bacarisse

Malcolm McLean said:
There is a big problem with syntax.

/* fine */
getpixel(image, x, y, &red, &green, &blue);
luminance = red * 0.3 + green * 0.6 + blue * 0.1;

/* unreadable */
Color col = object.getcolour( cursor.getx(), cursor.gety() );
object.set_Luminance( col.getrgbchannel(Color::RED) * 0.3 +
col.getrgbchannel(Color::GREEN) * 0.6 + col.getrgbchannel(Color::BLUE)
* 0.1)

But this is C++ (or something suspiciously like it). You could tidy up
the syntax a lot -- that is one thing that C++ will let you do.

You also seem to be comparing apples and oranges. It looks simpler to
write f(... x, y, ...) rather than f(... getx(cursor), gety(cursor),
....) but do you really have a pair of variables (global?) that track
the cursor position? (To keep on topic I have changed the code to be
a bit more C-like.)
The second is what object type code quickly ends up looking
like. There's nothing difficult going on. It purely the fact that the
symbols are nested too deeply.

Often, people don't use the full range of what is on offer in C. I
remember with joy when C acquired the ability to pass (and return)
structures. For small things like points and colours, this can help a
lot (with minimal cost):

Color col = pixel_get_color(object, cursor);
pixel_set_luminance(object, color_reduce(col, .3, .6, .1));

(I've C-ified what looks like C++).
 
M

Malcolm McLean

Ben Bacarisse said:
But this is C++ (or something suspiciously like it). You could tidy up
the syntax a lot -- that is one thing that C++ will let you do.

You also seem to be comparing apples and oranges. It looks simpler to
write f(... x, y, ...) rather than f(... getx(cursor), gety(cursor),
...) but do you really have a pair of variables (global?) that track
the cursor position? (To keep on topic I have changed the code to be
a bit more C-like.)


Often, people don't use the full range of what is on offer in C. I
remember with joy when C acquired the ability to pass (and return)
structures. For small things like points and colours, this can help a
lot (with minimal cost):

Color col = pixel_get_color(object, cursor);
pixel_set_luminance(object, color_reduce(col, .3, .6, .1));

(I've C-ified what looks like C++).
Unfortunately that hides a very evil trap.
Bool break libraries.

What do I mean by this?

Let's say we want to take an image. We then want to query it for the "light
spot", which is an application-specific algorithm for the point of greatest
luminance. Then when the cursor passes over that spot, we want to change it
to a cross.
Perfectly unexceptional requirement. The lightspot function has been
provided for us by some clever person. We are humble UI programmers.

Now.

Point2 WinSystem_getcursorpos()

great.

So
Point2 lightspot(Image *img)

OK.

In lightspot,h

#include <Winsystem.h>

Ah, not so good.

Let's make that

Point2 WinSystem_getcursorpos().

typedef struct
{
int x;
int y;
} Point 2D;

Point2D lightspot(Image *img).

Now lightspot() can be ported to a system not produced by the evil WinSystem
corp. But now we've got Point2 and Point2D swilling about our code, both
doing effectively the same thing, confusing everyone.
 
B

Ben Bacarisse

Malcolm McLean said:
Unfortunately that hides a very evil trap.
Bool break libraries.

What do I mean by this?

Let's say we want to take an image. We then want to query it for the
"light spot", which is an application-specific algorithm for the point
of greatest luminance. Then when the cursor passes over that spot, we
want to change it to a cross.
Perfectly unexceptional requirement. The lightspot function has been
provided for us by some clever person. We are humble UI programmers.

Now.

Point2 WinSystem_getcursorpos()

great.

So
Point2 lightspot(Image *img)

OK.

In lightspot,h

#include <Winsystem.h>

Ah, not so good.

Let's make that

Point2 WinSystem_getcursorpos().

typedef struct
{
int x;
int y;
} Point 2D;

Point2D lightspot(Image *img).

Now lightspot() can be ported to a system not produced by the evil
WinSystem corp. But now we've got Point2 and Point2D swilling about
our code, both doing effectively the same thing, confusing everyone

I've left everything unsnipped because I am not really sure where you
are going with this and I fear I'll delete come critical part. There
are some typos in the code and will have to paraphrase to see if I've
got you point.

Are you saying that in some cases when porting code one often has to
duplicate structures rather that simply mimicking them? If so, I am
not entirely sure why (my reading assumes that "Point 2D" should have
been "Point2D" and that it is distinct from "Point2").

Even if this is not what you are saying, I think you point will relate
to those situations where someone else has designed part of the API.
I was talking about those situation (sadly rather rare in practice)
where one gets to choose.
 
M

Malcolm McLean

Ben Bacarisse said:
Are you saying that in some cases when porting code one often has to
duplicate structures rather that simply mimicking them? If so, I am
not entirely sure why (my reading assumes that "Point 2D" should have
been "Point2D" and that it is distinct from "Point2").

Even if this is not what you are saying, I think you point will relate
to those situations where someone else has designed part of the API.
I was talking about those situation (sadly rather rare in practice)
where one gets to choose.
Bool break libraries.

Why? Because a graphics library has no business providing such a fundamental
type. IsMonochrome() is a function which naturally returns a boolean, but
unless the entire world agrees that GLBool_t is the name for it, it is just
a nuisance to everyone.

Structures are even worse because there is no implict conversion.

boolean monoFlag = true;

if( IsMonoChrome() == monoflag)
{
printf("This has a sporting chance of working\n");
}

You can't do that so easily if your structure is a 3D point. In any sort of
geometrical application points are a fundamental data structure. I've
rewritten any number of 3d graphics routines, simply because there was no
standard point type, which means no standard simple functions like length(),
dotproduct() and crossproduct().
 
C

CBFalconer

Malcolm said:
.... snip ...

Why? Because a graphics library has no business providing such a
fundamental type. IsMonochrome() is a function which naturally
returns a boolean, but unless the entire world agrees that
GLBool_t is the name for it, it is just a nuisance to everyone.

In C the general attitude is that zero represents false, and that
anything else represents true. If you want bit arrays you can then
complicate this.
 

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

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top