That would make more sense, since you can use the header to craft an interface in which some components are public and some are private.
Yes that tracks with the work I've done. It could even be so simple that only one value needs to be exposed through a getter (along with a couple "methods").
An ADC in an embedded system could operate like that.
You can, in a defined way (i.e., no invoking of UB). I just didn't put that in.
Clearly you can abstract away the parts of data that the API should not see.
They called this "modular programming" where "classes" were represented by opaque pointers and actions could only be performed on those pointers using the functions defined in the module.
What's the benefit of "opaque" pointers?
For background, my C code is almost entirely on microcontrollers. So I'm looking at it from that point of view. If you're talking about event-based applications running inside a full operating system, I've always stepped up to something like C#, so I don't have much experience with function pointers for that kind of work.
But consider the position of developer who implements a shared library that is distributed in binary form only. In this case, the benefit of opaque pointers for the development of a library is that the implementation remains private at the source level. One could, of course, reverse-engineer the binary but few people would do it.
If you define your structures in a public header -- and this includes C++ classes and templates with private members -- one can easily see the implementation and, with a few casts, start munging the guts of your objects and baking in a hard requirement on a specific layout and / or version of your library.
But for dynamic linking, this is how you avoid breaking ABI while maintaining forward flexibility.
struct PrivateFieldsOfMyShinyClass;
struct MyShinyClass {
int somePublicData;
double morePublicData;
struct PrivateFieldsOfMyShinyClass *p;
};
> malloc isn't possible, but neither are copies by value, sizeof() breaksThose are implementation details which are deliberately being hidden.
> Completely incompatible with inlining code.
They are as incompatible with code inlining as public/private modifiers in C++ are. That is, LTO is your best friend here. Also, have you ever tried to maintain binary compatibility with several versions of a third-party C++ library that keeps adding/removing private fields to/from its classes?
I know they're being hidden deliberately, but in C++ "private" doesn't break malloc (new), copies by value or sizeof. Or stack allocation, to add to the list.
> They are as incompatible with code inlining as public/private modifiers in C++ are. That is, LTO is your best friend here.
public/private aren't incompatible with inlining in C++. That is, you can call class functions that access private members and the compiler can inline those functions. Also, LTO is not always enabled by default, and doesn't always inline the things you want it to inline.
> Also, have you ever tried to maintain binary compatibility with several versions of a third-party C++ library that keeps adding/removing private fields to/from its classes?
I mentioned binary compatibility as one of the reasons one might want to do this. However, if you have a third party that doesn't care about API compatibility I doubt struct fields are the only thing they're going to change constantly.
At this point though, I think I honestly would prefer setters/getters.
struct MyClassPublic {
int x;
int y;
...
}
/* using it */
MyClass *myclass = myclass_create();
((MyClassPublic *)myclass)->x = 5;However, having only a boolean private/public access state isn't generally satisfying either. It often leads to violation of the principle of separation of concerns when all the functions (methods) acting on certain "private" fields need to live in the same class.
In simple classes, like std::vector, it's possible to get away with private. But in many cases that are more complex than that, it seems to me that the best approach is still to expose the data and to be just very clear about the exact purpose of each member.
struct public_stuff {
...
}
struct private_stuff {
struct public_stuff public;
...
}
struct public_stuff *make_public(/* ... */) {
struct private *prv = malloc(sizeof *prv);
/* ... */
return &prv->public;
}
Then when passed in to functions, cast the passed in struct pointer to the private one.The public struct doesn't even have to be at the start if one make appropriate use of offsetof and ensuring valid alignment.
Nothing new under the sun...
I didn't mean to imply it is, I lead with:
>> All too often someone, somewhere, on some forum … will lament the lack of encapsulation and isolation in the C programming language. This happens with such regularity that I now feel compelled to address the myth once and forever.
It's only about the myth that C doesn't have any level of encapsulation or isolation.
To be clear: you tried to say "C offers encapsulation/isolation". People read "This solution is equivalent to 'private'", an almost completely unrelated statement, and then respond to that.
That could typically be classified as a "Straw man fallacy"[0], but I believe people who do this in many cases simply do not have the necessary reading skills to understand what proposition has been made, and therefore honestly believe themselves to be reasoning correctly (i.e. without fallacy).
Reading comprehension used to be a topic at school when I was a child. I suppose that's no longer the case??
>That could typically be classified as a "Straw man fallacy"[0], but I believe people who do this in many cases simply do not have the necessary reading skills
Fyi... the author's article that this thread is about has in bold heading: "Myth: C has no equivalent to “private”"
So, a reasonable interpretation of the text following that headline is how to use C Language constructs to dispel that myth.
Doesn't seem like "straw man" applies here.
With all that said, I don't think all that condescending talk about the lack of reading comprehension or skills, without actually going into the arguments themselves, is really necessary or positive.
You are correct; I should change the heading to "Myth: C provides no encapsulation". I don't want to do it now, as the discussion is still ongoing and making this change now while people are commenting on the 'private equivalence" aspect would be gaslighting those people.
> Even then, my points still apply: this isn't really equivalent to how encapsulation works in other languages due to the lack of granularity and the "extras" of all the usual language behavior that stops being supported.
So? The myth the article addresses is "C has no encapsulation", not "C has great encapsulation".
That C provides stronger encapsulation and stronger guarantees for upgrade migration is the specific myth that I am trying to address.
Yes, you can do something in C that somewhat resembles encapsulation. You could also do it in Python with a decorator that inspects the caller. You can also do inheritance and virtual functions in C. You could do anything in a Turing complete language. But usually when talking about "X language has Y" refers to whether the language has Y as part of the specification. In this case it isn't encapsulation as part of the specification (that's why it comes with all those downsides/extras) but as an artifact of other aspects of the language, that's why I say it's not really equivalent.
struct Thing { int i; };
In an implementation c file: struct _Thing
{
struct Thing;
int b;
};
struct Thing *new_thing()
{
struct _Thing *thing = malloc..
thing->i = 0;
thing->b = 1;
return thing;
* It does not work on a field, but on the whole struct. Either all fields are public or all are private. The latter case forces you to write getters/setters for properties you want users to access, and that in C can be even more cumbersome as you need to write the definition in the .h and the implementation in the .c.
* It breaks, without an explicit and specific and error message, several actions. As it's mentioned in the article, malloc isn't possible, but neither are copies by value, sizeof() breaks... And those will break with an "incomplete type" error message, not a "this type is intentionally made private", which can add confusion in some situations.
* Completely incompatible with inlining code. Considering a lot of people still use C precisely for its performance, I think this can be a drawback in a lot of usecases.
I honestly think that hiding struct declarations should be done sparingly, and preferably limiting it to cases where it's actually necessary (for example, a library that doesn't expose internal struct fields so the same executable works with different versions of the dynamic library; or proprietary libraries that want to expose as little as possible). In the end it's still easy to bypass, and the distinction between header and code files already provide an indication of which functions you should use and which ones you shouldn't.