Question

I have a struct called State which holds all the variables for the program. Rather than being modified by functions directly, it is the value returned. Here is some code:

#define USERNAME_LENGTH 20
#define MAX_NUMBEROFUSERS 64
#include <string.h>
#include <stdio.h>

struct User {
    char name[USERNAME_LENGTH +1];
    int id;
};

struct State {
    int numberOfUsers;
    struct User users[MAX_NUMBEROFUSERS];
};

// create a new instance of state, called "_";
struct State _;

// function for returning the state with a new user in it
struct State newUser() {

    struct State state = _;

    char name[USERNAME_LENGTH +1];
    sprintf(name, "test username #%d", state.numberOfUsers);

    struct User newUser;
    newUser.id = state.numberOfUsers;
    memset(newUser.name, '\0', USERNAME_LENGTH +1);
    memcpy(newUser.name, name, USERNAME_LENGTH);

    state.users[state.numberOfUsers] = newUser;
    state.numberOfUsers++;

    return state;
}

int main() {

    while (_.numberOfUsers < 10) {
        _ = newUser();
    }

    printf("Users are:\n");
    for (int i=0; i<_.numberOfUsers; i++) {
        printf("%s\n", _.users[i].name);
    }
    printf("%d users total.\n", _.numberOfUsers);

    return 0;
}

Output:

Users are:
test username #0
test username #1
test username #2
test username #3
test username #4
test username #5
test username #6
test username #7
test username #8
test username #9
10 users total.

As you can see, the entire state is passed around until the program terminates. The newUser() function copies the state to a local variable which is modified and then returned. I chose not to pass the state as an argument to the function, because I wanted to be able pass other arguments if needed such as:

newUser(const char* username) {...}

Question: I think this is pretty cool, but is it practical? Can the compiler (GCC) optimize for this sort of almost-functional style?

I can see the benefit of explicitly keeping track of program state, but is it worth the trouble?

Was it helpful?

Solution

I can see why this is tempting, but to me it looks like a pretty good example of the Anti-pattern known as a God Object.

For a small enough program your global _ object would contain just a few members that are directly related to one another.

If your program were much bigger, it's unlikely that this would be the case. You might have a dozen members to support your help system, another half dozen dedicated to configuration, more dedicated to various forms of I/O, and so on. The only thing these members would have in common would be inclusion in this global object (or struct - makes no difference).

Furthermore, your application would become brittle - changing the type of one member would require you check the entire code base to see what would be affected.

Testing might also become "interesting" - any method/function anywhere can in theory change any part of the global state at any time.

OTHER TIPS

Given that you say you want to emulate functional style I would expect you to pass the current state in as a parameter:

struct State newUser(const struct State& _) {
    struct State state = _;

    //...

    return state;
}

Having this lets you create smaller sub state structs and only pass functions the state they care about.

However Each function call requires a copy of the entire state object. This results in a lot of copies of the state as modifications migrate up and down the stack. Dynamically allocating and sharing parts of the state that don't change often will help with that but will complicate the code with reference counting.

Licensed under: CC-BY-SA with attribution
scroll top