First and foremost, I *sick* to death of reading "if you don't like it,
use something else" like I'm some kind of a f***tard or whiney s'kiddy.
I’m not. I’m a serious developer, attempting to explore an important
subject in an intelligent way. I’m fed-up of reading:
- It’s alright for me.
- If you don’t like it, use something else.
- Maybe C isn’t the language for you.
- That’s not the way things are done at the moment (so we’re not going
to do them that way in the future.)
- Write an add-on library to…
Understand the context:
The SDL API IS CHANGING. It will likely continue to change. This is
tacit acknowledgement that “the current way” isn’t quite right.
If you remember, the topic of this thread is “Opaque Objects /
Pointers”. Implicitly, you can add “should they be used in SDL?”.
As for the C language… OMG! I WISH I didn’t know C so well, because it
wouldn’t PISS ME OFF AS MUCH WHEN PEOPLE INSINUATE THAT I DON’T KNOW
WHAT I’M TALKING ABOUT!
STOP taking swipes at me and answer the sodding question!
Now that I’ve got that off my chest:
David Olofson wrote:
Sorry, what I meant was that when it comes to implementation, one may
bring the other, though the concepts are theoretically orthogonal.
For example, you can shift the responsibility of chosing the
correct “destructor” into a library by having a generic Destroy()
function that takes a void * or similar argument - but that means you
have to use some kind of object model that allows the Destroy()
implementation to figure out who to call to get the job done.
Well, that’s actually a pretty hard problem in real applications. How
deep is a deep copy…? Maybe you need refcounting and copy-on-write
for performance reasons, or maybe that would just be a waste of
cycles?
This is something that has to be decided on the
application/engine/higher level library level, and I think building
that sort of stuff into a low level API like SDL would only confuse
matters and make it harder to use properly.
Basically, when it’s not clear what the correct way of doing something
is, it’s better to leave the decision to the application code.
Right, get you now.
What one could do is specify a large structure that the user may want
many copies of, say a generic structure for holding pixel data.
Now, this structure could be so large that no developer would ever in
practice pass it by value.
So, as a library developer, what do you do? You COULD make the developer
do this:
PixelStruct ps;
InitPixelStruct(&ps, arg1, arg2, arg3);
or
PixelStruct *ps = malloc(sizeof(PixelStruct));
InitPixelStruct(ps, arg1, arg2, arg3);
Or, alternatively, you could just acknowledge the fact in your API and
give them something simple and easy to use, like this:
PixelStruct *ps = NewPixelStruct(arg1, arg2, arg3);
Now you can argue until the cows come home about HOW NewPixelStruct is
implemented (whether malloc’ed or a static array or some other
memory-pooling scheme), but most of the time, the user won’t really be
bothered, because, at the end of the day, there’s a VERY good chance
that they’d just do it the way you would. And if they need a really
specific scheme, then they’ll either:
1. Just write it themselves
or
2. Ask you to write a similar function that implements a different
scheme.
or
3. Use something else
My point, is that by using a 3rd party library in the first place,
you’re prepared to sacrifice some level of control over the
implementation details, for the time, effort & COST savings that you
get by not having to write it all yourself.
Now the purpose of this example is to make this point: If you’re going
to provide a library function and you’re NOT prepared to say “This is
how we’ve decided to implement this”, then you may as well give up and
go home.
Of course, what you could do is implement a function one way, get
feedback from your customers and if they don’t like it, or lets say, you
come up with a more efficient method on your own, then you can always
replace the implementation and it will be transparent to your customers
(because you’ve not changed the API…).
But then, of course, unless they’ve profiled their code and highlighted
that library function as a particular bottle-neck, then they probably
won’t even care.
When was the last time you really cared about the implementation of
say, printf() on a desktop system? You don’t. All you care about is that
it’s there and it works.
As a user of an API that ALL you generally care about; does it do the
job I want? Is it easy to use? The rest, you trust the developer to make
sensible choices on your behalf. Of course, if they’ve released the
source to you, say, under LGPL, then you can always check it, to satisfy
yourself.
[…]
Personally, I dislike APIs that try to wrap things like this. C++
APIs that “abuse” operator overloading to do this are even worse.
It can be “handy” and (in the case of C++) make the code look
cleaner, but for the most part, it just means you have to double
check everything all the time to be sure what’s actually going on.
Is that really the case? The reason I chose SDL_Surface as an
example, is that it’s not immediately obvious how to clone it just
by looking at a list of functions (okay, could be ‘fixed’ by
documentation, but surely that is the definition of ‘less
intuitive’).
Well, if you step back and look at the problem: When do you want to
clone an SDL_Surface? If you ever want to do it, why?
It turns out that it’s pretty much a non-issue, because the few times
you actually do want a copy, you actually want a slightly different
version, such as a version suitable for fast blitting to the screen
(SDL_DisplayFormat*()), or a temporary version in some specific,
handy format for some direct pixel level manipulation.
So, if it’s a rare thing, you’re not really gonna be fussed if it
isn’t particularly ‘quick’ are you? But when you DO need it, you’re
probably gonna be pissed that it’s not there at all.
Also, the documentation states:
/* This structure also contains private fields not shown here
*/
So it is at least semi-opaque already. Why not define the API to
include functions for querying the (almost exclusively) read-only
data?
Because it’s just more typing, and doesn’t really prevent anything but
silly typos…?
If the documentation states that these fields are read-only, that
pretty much covers it, IMHO.
Okay, but my experience is that if you give someone opportunity to do it
wrong, then they will. Hell, I’ve seen someone copy a string constant
error message into a temporary buffer using strcpy() before displaying it.
Yes, you can NEVER legislate for people who really don’t know what
they’re doing, but you CAN make it harder for them to do something
stupid (which you’ll get blamed for, of course!)
And it does, at least to me, seem 100% more obvious what surface
refers to in (1) when compared to (2). Again, the majority of
overhead of development is in maintenance, so the pain of typing a
really verbose statement, like (1) is surely off-set by the saving
you’ll get when the code is re-visited?
You’re forgetting that SDL is a pretty low level API, that is
sometimes getting dangerously close to the dreaded “inner loops” of
performance critical software. Turning raw field accesses into
function calls - or even inlines that can potentially confuse the
optimizer - needs to be strongly motivated.
But what about using function-style macros?
How do you actually hide pointers when dealing with a C API in C?
(Yeah, I know it’s possible, and I’ve even tried it in some of my
older APIs - but it tends to cause more problems than it solves.)
How so?
The issue, in my view, is that C lacks explicit support for it.
Can this be worked-around in a way that users find easy to
understand, use, etc?
Well, you can make all structs “private” and provide function
calls to access the information instead, but all you gain from
that is some minor extra freedom in changing SDL internals without
breaking source and/or binary compatibility with applications, at
the cost of potentially significant overhead.
Yes, that’s the problem isn’t it… this is basically the problem
Stroustrup had when he first started to develop C++; people started
to circumvent the protection mechanism in order to avoid the
overhead of a function call, just to get at a member value, such as
an int.
It’s a problem C was never designed to solve, so it’s kind of hard to
solve it in C without adding overhead or making things cumbersome…
I just don’t think it’s worth the effort.
Again, goes back to the library trade-off and the shifting
responsibilities thing; if 99% users of your library are going to do
something you didn’t do because of ‘efficiency’ concerns, then you’re
not actually saving anything.
Incidentally, I’m not talking about all structs, for example,
SDL_Rect is simple (and small) enough to be perfectly understandable
and safe as it is, making it opaque would just be silly.
SDL_Surface, on the other hand, has up to 3 levels of indirection
(Surface->PixFormat->Palette->Color) and that’s just in the public
part!
You need that information exposed anyway, to be able to do pixel level
access from application code. Maybe you never need that, and maybe
SDL just isn’t the right tool for the jobs you do?
Then if you had a opaque struct, you’d have an API for exposing that
data! The point about the levels of indirection was in regard to
deep-copying.
An opaque struct doesn’t mean you can’t access the data you need, it
means you can’t access it using the a->member syntax.
Eddy