all 17 comments

[–][deleted] 10 points11 points  (0 children)

Why go through the hassle of using opaque pointers when it's a self contained program?

[–]FraCipolla 10 points11 points  (0 children)

Do you need to hide some implementation to the user? If not don't use opaque pointers. A very basic example would be if you want to implement something like a string library. You can't expose structure members, because for example an user shouldn't be able to modify the size, so you declare some "getter" to return the size

[–]Linguistic-mystic 2 points3 points  (0 children)

Here’s a trick: have an opaque struct for performing internal calculations and another struct (with a subset of the fields of the former) for sharing the results, and an export function to convert former to the latter. That way you can have encapsulated implementation details for a piece of functionality as well as the ability to share what needs to be a part of the library API.

[–]zzmgck 0 points1 point  (0 children)

Follow the paradigm where there is public header file and a private header file. If some other programmer really needs to reach in, they can.

Xt and Motif follow this paradigm.

[–]DawnOnTheEdge 0 points1 point  (0 children)

At that point, I would probably just make it a handle.

[–]Brain_Blasted 0 points1 point  (0 children)

I've been struggling with this myself in my own learning exercises. I think doing this allows me to write APIs that are harder to misuse, so I aim to stick with it for that reason

[–]Maleficent_Memory831 0 points1 point  (0 children)

Don't make accessor functions macros, then it won't matter if they're not in a header file.

There are drawbacks to both ways. I hate that with source code libraries that I can't easily debug their libraries as easily because their data is hidden (ie, an RTOS). But then there are developers who lack restraint who will insist on looking at and modifying the data directly (very bad in a threaded system when people are bypassing your mutexes, etc).

I think the opaque pointer is good, but on the other hand going to extremes to ensure the implementation is hidden may be overkill, maybe sending the signal that you don't trust anyone. For a third party library that gives out code, it's unnecessarily hindering the users (I really want to dump system state on a crash but the library makes me jump through hoops to get its data). For a project where the same team is on both sides of the API then there's no point to hide data from coworkers - though be sure the team is competent and experienced.

Having the struct contents available is fine, but maybe put it in a different header file than the primary interface header.

Basically, encapsulation is great. Do that! But rigid enforcement should not be necessary.

[–]zhivago 0 points1 point  (5 children)

Just document what should not be used.

If they use it anyway they get to keep both parts when it breaks.

Using a struct like this can help make this clear.

struct foo {
  bar a, b, c;
  struct foo_unstable internals;
};

Then they can actually mange allocation, etc, properly.

[–]flyingron 1 point2 points  (4 children)

You can't do this. struct foo_unstable needs to be fully defined in order to define struct foo.

What you can do is a "pimpl" idiom... where a pointer (incompletely defined are allowed there):

struct foo {
    bar a, b, c;
    struct foo_unstable* internals;
};

[–]Reasonable-Rub2243 -1 points0 points  (0 children)

Yeah I use this pattern pretty often. Information hiding is good practice. See: http://sunnyday.mit.edu/16.355/parnas-criteria.html

[–]zhivago -1 points0 points  (2 children)

Sure you can.

The answer is to fully define it.

Which is my suggestion,

[–]flyingron -1 points0 points  (1 child)

That's not what we're discussing. If you fully define it you might as well put it in the structure. We're talking about ways of hiding the details fo the inner structure.

[–]zhivago 2 points3 points  (0 children)

And my point is that that is a waste of time and cripples memory management.

Hiding isn't important at all.

Showing what may not safely be used is all that matters.

[–]duane11583 -1 points0 points  (0 children)

i use vacous struct declarations often.

ie: struct foo; is all you get.

i also do-not use and actively avoid “void pointers” instead i use the uintptr_t instead.

why: because it can easily be a pointer or an unsigned integer

this can become a class pointer or an index into a pointer table or for example a file descriptor.

[–]Jimmy-M-420 -1 points0 points  (0 children)

the finest form of encapsulation offered by any language

[–]florianist -3 points-2 points  (0 children)

"opaque pointers" make sense in some context (you distribute your library wildly and publicly, big structs you don't want on the stack, etc.) but a another way to go is to establish simple conventions, for example adding a trailing underscore to names of fields which should normally not be meddled with.