Search code examples
csingletonpass-by-reference

How to implement singleton object in C?


I have a requirement for an OBJECT to be used by other objects. The requirement is that the OBJECT not require initialization by any external call. So I began with something like so...

//  .h
struct My_Shared_Obj {
  bool is_happy;
  struct foo *foos;
};

typedef struct My_Shared_Obj *mysharedobj;

//  .c
static mysharedobj MSO = NULL;

mysharedobj __instance();
void __mso_do_something();

void __mso_do_something() {
  mysharedobj _mso = __instance();

  // now do something with struct members
}

mysharedobj __instance() {
  if(MSO == NULL) {
    // initialize MSO
  }

  return MSO;
}


// we have an 'API' as follows
const struct MSO_API API = {
  .do_something = &__mso_do_something
};
  1. Is this the best way to implement a self-initializing object?
  2. Reference behavior doesn't seem to make changes to MSO but only for the scope of the function

Additionally, there are functions within this same source that are only accessible through the API ... ex:

static mysharedobj MSO = NULL;

/*
  This method makes an assumption that since it can ever only be called by an API function
  that the __instance() function has been invoked and that MSO will be a valid object.
*/
void __foo_add(foo *ptr) {
  // assume we have next incrementing somewhere
  MSO->foos[next] = *ptr;
}

void __mso_do_something() {
  struct My_Shared_Obj _mso = __instance();

  // hypothetical condition; assume we have a function that provides a foo for us...
  if (!_mso->is_happy) {
    __foo_add(get_a_foo());
    _mso->is_happy = true;
  }
}

I'm having difficulty getting the MSO object to be correctly referenced. The examples above are not contrived. It is what I have tried (names have been changed to protect the innocent). I have been experimenting with this for a bit now and not getting the results I need.

EDIT:
Corrected some typos. For what it's worth, the work I'm doing is not concerned with constraints of ISO standards. Thanks for the considerations, though.


Solution

  • I have a requirement for an OBJECT to be used by other objects. The requirement is that the OBJECT not require initialization by any external call.

    If the initial values of all members can be written as constant expressions

    ... then just declare the object itself, with an initializer:

    static struct My_Shared_Obj the_object = { .is_happy = 1, .foos = NULL };
    

    That initialization happens as if before the program ever starts running, so no function can see an uninitialized value.

    Within the same translation unit, you can access the object directly. If you want to expose it outside the TU, then also provide an external function to do so:

    struct My_Shared_Obj *get_mso(void) {
      return &the_object;
    }
    

    Alternatively, if you don't make the variable static then you don't even need an accessor function. Just provide a header that declares it for other TUs as extern, without an initializer:

    extern struct My_Shared_Obj the_object;
    

    . Then everyone can access it directly. And if you're considering objecting to using global variables, then do first sit back and have a think about how "singleton" is just another name for that anyway, possibly gussied up with a little lipstick.

    If any initial values require run-time computation

    ... then you need to gate all access to the object via a function that tests whether to initialize it first. In this case, it makes sense to make the structure itself a static local variable of that function, and you can provide another that specifies whether to initialize:

    struct My_Shared_Obj *get_mso(void) {
        static struct My_Shared_Obj the_object;
        static _Bool is_initialized;
    
        if (!is_initialized) {
    
            // ... initialize the_object ...
    
            is_initialized = 1;
        }
    
        return &the_object;
    }
    

    Defining the shared object inside the function gives it no linkage, so that even other functions in the same TU can't access it without ultimately going to get_mso() to obtain a pointer to it.

    Do note that if you need thread safety then you'll need to do some extra work. There are several way to do that, depending on details of the environments you need to support.


    1. Is [the approach presented in the question] the best way to implement a self-initializing object?

    There is no way in C to have a self-initializing object, because in C, objects are data only. They have no behavior.

    Also, no, the approach describe is not the best way to implement at-need initialization under the conditions you describe (see above in this answer).

    Moreover, this ...

    mysharedobj *__instance() {
      if(MSO == NULL) {
        // initialize MSO
      }
    
      return MSO;
    }
    

    ... is wrong. Your MSO has type mysharedobj (a.k.a. struct My_Shared_Obj *, confusingly hidden behind a typedef), but the return type of the function is mysharedobj *, which is different. C at least requires a cast, though some compilers might nevertheless do the conversion implicitly and silently. (But do you really not get at least a warning?)

    More importantly, if another function uses the pointer returned by that function as the struct My_Shared_Obj ** that the function promises it to be, then undefined behavior results. If you're lucky, the program will crash with a segfault or some similar memory-related error, but it could as easily seem to work as expected, or almost so. You could very easily see surprising behavior if some code uses MSO directly, while other code uses the pointer obtained from __instance().

    1. Reference behavior doesn't seem to make changes to MSO but only for the scope of the function

    C does not have references, and "reference behavior" is not a standard part of C jargon. Thus, it's not clear what you expect, but also not clear what you observe. Possibly, though, you're seeing effects of the issue described in my immediately preceding comments.