You can’t have fields in the public instance structure if you’re using the G_DECLARE_DERIVABLE_TYPE macro, by design. Public instance fields are Bad, Actually™️ and you should never expose them, because people will be able to modify them behind the back of the type.
If you want to have fields associated with your instance, use G_DEFINE_TYPE_WITH_PRIVATE macro to add private instance data to your type.
You should read the GObject tutorial, which has full examples of the best practices for writing GObject types in C.
But then there is no way to expose my private struct.
I am creating a derivable class and I would like to have two headers, one named xyz-foo-bar.h (in which XyzFooBarPrivate is an opaque type) and another named xyz-foo-bar-private.h (in which XyzFooBarPrivate is fully exposed). If you are using my widget you include only xyz-foo-bar.h and my private data will be opaque to you, but if you are creating a widget derived from my widget you must include both headers to have access to the private data. GtkWidget does exactly that.
I normally create final types, but since I am working on a generic container (the “flow” container I was talking about in a previous conversation), it makes totally sense to declare it as a derivable type.
Of course, that’s the whole point of a “private” structure.
What? No, it does not.
GtkWidget does not expose its private structure, or provide direct access to its private data structure; you only have access via functions.
Exposing fields in the instance structure is a great way to get ABI compatibility issues in a library, as well as allowing users of your code to poke directly at the internals with unknown side effects. We spent the whole of the GTK2 cycle trying to get rid of this behaviour, and it was one of the major drivers of the GTK3 API bump.
The G_DECLARE_* and G_DEFINE_* macros encode the best practices for writing GObject in C, and are the result of 20+ years of work, so you better follow them.
No, it doesn’t really make sense to have derivable types that encode a layout or container policy; it’s the very opposite. Derivable types should be at the top of a hierarchy, and offer minimal behaviours. That’s why most of the classes in GTK4 are final.
I am still deciding what should be derivable of what I created, whether my flow widget, or its layout manager, or both. But this decision is influenced by what I am able to expose.
The layout manager is what takes care of allocating the children widgets. To do that it needs access to a lot of properties every second (things like "spacing", "leading", "orientation", etc.). I feel that using functions to access those might slow everything down compared to direct access.
But also knowing what I can expose will influence whether my layout manager can be reusable or, instead, forever tied to a flow widget.
Another possibility that I am considering (and maybe it will be my final choice) is that of getting rid of the layout manager and letting my widget be derivable without exposing its private data.
That’s just legacy, the result of GTK predating all of the macros. The priv pointer in the instance structure is not really used inside GtkWidget itself, because using the get_instance_private() function is actually faster than the double pointer redirection, and only project accessors use the private data to avoid a function call. This is mainly a performance optimisation for GtkWidget, but it’s not required by anything else inside GTK itself.
You should only ever expose properties, and accessor functions. Those are things that allow you to properly manage an ABI and backward compatibility.
Before creating my flow widget I studied the code of GtkOverlay (and later GtkBox). GtkOverlay uses a layout manager, but not really, because all that this layout manager does is emitting a custom signal ("get-child-position") that is captured by the GtkOverlay widget, and this will take care of finding the right position for the child.
This in my opinion breaks a bit the idea of a separate layout manager, which should be able to allocate things without delegating the task back to its widget.
So in my code I did not use the same machinery. The layout manager actually does all the calculations, but still needs access to several flow widget’s properties. At the moment I am keeping both objects (both the widget and its layout manager) inside the same .c file, so I can use *get_instance_private() in both directions. But if I want to think big and make both the layout manager and the flow widget derivable, they need to be able to work in more flexible scenarios.
Or, as I mentioned earlier, if I don’t find any side effect against removing my layout manager it might become my final choice, which will solve the problem of having a non-reusable layout manager.
Yes, but that is accessible only from the .c document that G_DEFINEd the class…
In this specific case: performance for whomever wants to derive my class (direct access of properties vs. function call).
Still concerning this specific case, I believe I have found the most elegant solution possible: moving all the relevant “flowing” properties inside my layout manager’s private struct. In this way no direct access is needed, since the layout manager is the only producer and consumer of these data. An amazing consequence of this approach is that the layout manager becomes reusable (i.e. it will make any container that adopts it look like a “flow” in the way its children are allocated, not only my “flow” widget – basically the real flow functionality is now all in a reusable layout manager).
Going out of this specific example and speaking in more philosophical terms, I tend to believe that any public API/interface/class/etc. should have as many things exposed as possible. For two main reasons:
I trust people and what they do with what they have
Exposing everything tends to constitute a booster for the quality of an interface: the programmer will be forced to make sense of a larger amount of different scenarios, and thanks to Postel’s law that will tend to create more robust code
But going back to this specific example… as I said I have solved the case