GNOME Bugzilla – Bug 543189
static constructor not called when class is accessed via static methods
Last modified: 2018-05-22 13:08:34 UTC
The static construct block of a class is executed before a new object of that type is created. However I would expect it to be executed when static methods are called. Example: /* * valac test.vala */ using GLib; public class Statico : GLib.Object { public static int test_value; static construct { test_value = 5; } public static int get_test_value() { return test_value; } } public class Test : GLib.Object { public static void main(string[] args) { /* Should print '5', but prints '0' */ stdout.printf("%d\n", Statico.get_test_value()); } }
*** Bug 543190 has been marked as a duplicate of this bug. ***
Confirming.
*** Bug 565852 has been marked as a duplicate of this bug. ***
static construct is relative to the class initialization, shouldn't it be "class construct" then the method should be a class method? In that case it has sense to have the class initialized. Anyway, keeping the static thing, all static methods have to call g_type_class_ref/unref in order to create the class then initialize it. It is somewhat forced to have "static construct" depend on class initialization. What about having a good ondemand system for initializing static values and calling static construct? I'd suggest a static construct that doesn't depend on class_init and a static boolean to check whether it's initialized or not. Then for each property/method prelude call this initialization. This call will go also in the class_init.
Both, static construct and class construct, exist. static construct is never called more than once, however, class construct is called for every subclass that is being initialized. With your proposal, public static fields would still not always be initialized as the init function can't be called from the outside.
*** Bug 577149 has been marked as a duplicate of this bug. ***
*** Bug 621944 has been marked as a duplicate of this bug. ***
You can work around this by doing g_type_class_ref(MY_TYPE); in C, or typeof(MyType).class_ref(); in Vala. The class_ref() function tells the GObject run-time to initialise the class if it hasn't already done so. Perhaps it would be worth the Vala compiler adding a g_type_class_ref/unref at the start/end of every static method? Vala already does a lot of refcounting at function entry/exit, so it would be unlikely to make any difference performance-wise.
Maybe ref/unref in each static method isn't needed... It should be done just once. I'd do something like this (when a static construct is set): static void foo_static_construct(void) { if (g_type_class_peek(TYPE_FOO) == NULL) { g_type_class_unref(g_type_class_ref(TYPE_FOO)); } } void foo_method1 (void) { foo_static_construct(); // ... } void foo_method2 (void) { foo_static_construct(); // ... } This will cause the _init function to be called just once and the ref/unref procedure is done only on first static method call.
Created attachment 183335 [details] [review] codegen: always init class if it has a static constructor when using static methods This patch does what I've explained above... Basically a new "static init" function is defined as: static void foo_static_init (void) { if (g_type_class_peek (TYPE_FOO) == NULL) { g_type_class_unref (g_type_class_ref (TYPE_FOO)); } } So when a static constructor si defined, each static method is generated as: void foo_method (void) { // variable initializers foo_static_init (); // function body } Even if it's a workaround, I guess that this is the only possible way to get properly working static constructors with the current GType implementation.
Not sure if you consider it to be the exact same problem (though the solution/workaround would be almost the same), but the problem also exists when having static members that are implicitly dynamically set: public class Test : Object { private static int[] array = { 1, 2, 3 }; public static void func () { stdout.printf ("%d\n", array.length); } } public static void main () { /* should print 3, but prints 0 */ Test.func(); } This is because the array (and its size) is created and set in class_init(), which is obviously not called before the static method. Here too, guarding the static method with a "simple" class_ref() would do the trick. Actually, this applies to any public static method or class member, e.g. anything that has a chance to be accessed before the class get instantiated.
Calling the already generate foo_get_type() function would be less work. static void foo_method (void) { // variable initializers foo_get_type (); // function body }
(In reply to comment #12) > Calling the already generate foo_get_type() function would be less work. > > static void foo_method (void) { > // variable initializers > foo_get_type (); > // function body > } This wouldn't work: get_type() doesn't cause the class to be initialised. You either need to create an instance, or call g_type_class_peek().
It's worth noting that instantiation of static members is a tricky thing in C++; in that case, constructors are called before main(), and the order in which it happens is more-or-less random (or rather, "implementation defined"). Does anyone know how it's done in C# and Java?
in java, if we try to call Klass.staticMethod, the class loader will load Klass first, and any static class members will be defined and initialised, and any static code block will be executed. the static class member initialization/code block execution follows FIFO rules. for example: class Test { private static String aStr= "1"; static { aStr = "2"; } public static void func(){} } if we call Test.func(), first aStr is defined and initialised with value "1", and then aStr is assigned with value "2"
*** Bug 686337 has been marked as a duplicate of this bug. ***
I still think the best approach for this is to generate a g_type_class_ref()/unref() pair at the start and end of each static method to demand-create the class if no instance has yet been created. This would have the same effect as bruce describes in Comment 15 -- it would execute the static construct block if it hasn't been run yet. It still wouldn't solve the problem of accessing static *fields*, but we could get around that by forbidding public static fields, making the use of an accessor function mandatory. For example: class OtherClass : Object {} class MyClass : Object { private static OtherClass a_class; // May not be public static construct { a_class = new OtherClass(); } public static get_other_class_member() { // Generated C code for this method calls g_type_class_ref() here, // which runs the static construct block if necessary return a_class; // Generated g_type_class_unref() here } } Other than the minuscule performance impact of the extra ref/unref, can anyone think of a downside to this approach?
(In reply to comment #17) > I still think the best approach for this is to generate a > g_type_class_ref()/unref() pair at the start and end of each static method to > demand-create the class if no instance has yet been created. > > This would have the same effect as bruce describes in Comment 15 -- it would > execute the static construct block if it hasn't been run yet. > > It still wouldn't solve the problem of accessing static *fields*, but we could > get around that by forbidding public static fields, making the use of an > accessor function mandatory. You can't make this mandatory for BC reasons. Rather than doing the ref/unref inside the static method, I'd rather do it when accessing static stuff: i.e. add the boilerplate to the caller. Anyway I'm not fond with the idea to fix this particular issue. Due to the nature of C and gobject, I feel this will be more painful in the future than what it is currently.
Is important Vala users to understand better C and GObject, in order to select a possible solution. This is an enhancement, because Vala task is to help not to solve all issues. Vala performs well for common approaches. This is more advanced GObject use case and may be specific for not general cases. There should be a point where Vala should be put aside and write pure C class in order to get more control on construction/destruction of your classes. For general use cases, Vala cover almost all of them.
Moved to unspecified version. Because it is a problem for all of them.
-- GitLab Migration Automatic Message -- This bug has been migrated to GNOME's GitLab instance and has been closed from further activity. You can subscribe and participate further through the new bug through this link to our GitLab instance: https://gitlab.gnome.org/GNOME/vala/issues/11.