A private member is a member that is only accessible by other methods of the class.
Q: Why private? It is inconvenient, isn't it?
A: Exposing all details may offer some convenience, but it is almost always undesirable.
Q: Why?
A: You may allow your users to directly access your private data and you suddenly changed the internal structure of your class and the user code no longer work.
The practical way is to introduce "private data". Private data are stored separately from the public data so that changing the private data does not affect the interface of the class. Other code using this class will stay working as long as the public data are not changed.
See the
Bridge Pattern, a famous design pattern.
GObject provide mechanism to add "private struct" to a class.
This is the full code which you can safely skip.
/* A fundamental type. Instantiatable. Has private struct. */
#include <stdio.h>
#include <glib-object.h>
typedef struct {
GTypeClass something_as_boilerplate;
} myclass_t;
typedef struct {
GTypeInstance something_as_boilerplate;
int an_instance_member;
} myinstance_t;
typedef struct {
int a_private_instance_member;
} myinstance_private_t;
void my_class_init_func(myclass_t* klass, gpointer data) {
g_type_class_add_private(klass,sizeof(myinstance_private_t));
}
void my_instance_init_func(myinstance_t *instance, gpointer data) {
myinstance_private_t *priv;
priv = G_TYPE_INSTANCE_GET_PRIVATE(instance,G_TYPE_FROM_INSTANCE(instance),myinstance_private_t);
priv->a_private_instance_member = 81;
}
int main() {
g_type_init();
GTypeInfo my_type_info = {
sizeof(myclass_t), //guint16 class_size;
NULL, //GBaseInitFunc base_init;
NULL, //GBaseFinalizeFunc base_finalize;
/* classed types, instantiated types */
(GClassInitFunc)my_class_init_func, //GClassInitFunc class_init;
NULL, //GClassFinalizeFunc class_finalize;
NULL, //gconstpointer class_data;
/* instantiated types */
sizeof(myinstance_t),//guint16 instance_size;
0, //guint16 n_preallocs;
(GInstanceInitFunc)my_instance_init_func, //GInstanceInitFunc instance_init;
/* value handling */
NULL, //const GTypeValueTable *value_table;
};
GTypeFundamentalInfo my_fundamental_info = {
G_TYPE_FLAG_CLASSED | G_TYPE_FLAG_INSTANTIATABLE
};
GType my_type_id = g_type_register_fundamental(
g_type_fundamental_next(),
"MyTypeWithPrivate",
&my_type_info,
&my_fundamental_info,
0
);
printf("%d\n",my_type_id);
printf("%s\n",g_type_name(my_type_id));
myinstance_t *inst = (myinstance_t*)g_type_create_instance(my_type_id);
myinstance_private_t *priv = G_TYPE_INSTANCE_GET_PRIVATE(inst,my_type_id,myinstance_private_t);
printf("%d\n",priv->a_private_instance_member);
priv->a_private_instance_member = 90;
printf("%d\n",priv->a_private_instance_member);
g_type_free_instance((GTypeInstance*)inst);
return 0;
}
First let's see the structs.
typedef struct {
GTypeClass something_as_boilerplate;
} myclass_t;
typedef struct {
GTypeInstance something_as_boilerplate;
int an_instance_member;
} myinstance_t;
typedef struct {
int a_private_instance_member;
} myinstance_private_t;
There is only one class, which has three separate structs now.
- myinstance_t, the instance struct, stores public data.
- myinstance_private_t, the private data struct, stores private data.
Now let's tell GObject about the existance of such a class. You may skip this.
GTypeInfo my_type_info = {
sizeof(myclass_t), //guint16 class_size;
NULL, //GBaseInitFunc base_init;
NULL, //GBaseFinalizeFunc base_finalize;
/* classed types, instantiated types */
(GClassInitFunc)my_class_init_func, //GClassInitFunc class_init;
NULL, //GClassFinalizeFunc class_finalize;
NULL, //gconstpointer class_data;
/* instantiated types */
sizeof(myinstance_t),//guint16 instance_size;
0, //guint16 n_preallocs;
(GInstanceInitFunc)my_instance_init_func, //GInstanceInitFunc instance_init;
/* value handling */
NULL, //const GTypeValueTable *value_table;
};
GTypeFundamentalInfo my_fundamental_info = {
G_TYPE_FLAG_CLASSED | G_TYPE_FLAG_INSTANTIATABLE
};
GType my_type_id = g_type_register_fundamental(
g_type_fundamental_next(),
"MyTypeWithPrivate",
&my_type_info,
&my_fundamental_info,
0
);
Nothing special.
The secret lies in the class init function.
void my_class_init_func(myclass_t* klass, gpointer data) {
g_type_class_add_private(klass,sizeof(myinstance_private_t));
}
So simple! Just tell GObject that there is a private struct for my class and pass in its size as an argument as well. Then the private struct will be allocated together with each instance.
Q: How to access the private struct?
A: see:
void my_instance_init_func(myinstance_t *instance, gpointer data) {
myinstance_private_t *priv;
priv = G_TYPE_INSTANCE_GET_PRIVATE(instance,G_TYPE_FROM_INSTANCE(instance),myinstance_private_t);
priv->a_private_instance_member = 81;
}
Use the G_TYPE_INSTANCE_GET_PRIVATE macro to extract the private struct from an instance.
That's it.
Note that you can call G_TYPE_INSTANCE_GET_PRIVATE anywhere in your program, even outside any methods of the class. Since the C language does not understand "class" or "private", it makes no different what is "inside" a class or "outside" a class. That's to say, your private struct is accessible anywhere.
To further keeping the user from messing up with your private struct, you can provide the definition of the public struct in the header file (*.h) and keep the definition of the private struct in the implement file (*.c) and keep the private struct undocumented so that the users do not know what is inside the private struct.
This still does not stop the users if they copy your source code if your program is open-sourced, but their programs may break at any time since you can change the private struct without a warning. It is not your fault. It relies on the good habit of programmers to write good programs.