2

In Python, its possible to create a derived property from a class using the @property decorator for example

class State():
    def __init__(self, fav_num_monday, fav_num_not_monday, is_monday):
        self.fav_num_monday = fav_num_monday
        self.fav_num_not_monday = fav_num_not_monday
        self.is_monday = is_monday

    @property
    def fav_num(self):
        return self.is_monday * self.fav_num_monday + \
            (1 - self.is_monday) * self.fav_num_not_monday

state = State(12, 5, 0)
print("Current favourite number: %d" % state.fav_num)

My question is then what is the best way to achieve this in C (where speed is of the utmost importance). I've have added below some ways I have tried but am not sure if they could have repercussions in a larger codebase. They are as follows:

  1. Simply writing out the whole expression each time. Pros: No unexpected repercussions, no code/speed penalty. Cons: Ugly code, takes a long time to write.
  2. Using a get function. Pros: Code easier to read. Cons: Inefficient code (slower than 1).
  3. Defining a macro. Pros: No code/speed penalty. Code quick to write. Cons: Potential repercussions later, code not so easy to follow.

The example program is below

#include <stdio.h>
#include <string.h>

#define state_fav_num  state.is_monday * state.fav_num_monday + (1 - state.is_monday) * state.fav_num_not_monday

struct State {
    int fav_num_monday;
    int fav_num_not_monday;
    int is_monday;
};

int get_state(struct State *state, char *property) {
    // Returns value of the property in state. 
    // Allows us to create derived properties also.
    if (!strncmp(property, "fav_num_monday", 14)) {
        return state->fav_num_monday;
    } else if (!strncmp(property, "fav_num_not_monday", 18)) {
        return state->fav_num_not_monday;
    } else if (!strncmp(property, "is_monday", 9)) {
        return state->is_monday;
    } else if (!strncmp(property, "fav_num", 7)) {
        return state->is_monday * state->fav_num_monday +
            (1 - state->is_monday) * state->fav_num_not_monday;
    }
}

int main() {
    // Set the state.
    struct State state;
    state.fav_num_monday = 12;
    state.fav_num_not_monday = 5;
    state.is_monday = 1;

    // Print favourite number in different ways.
    printf("\n1) Current favourite number is %d.",
        state.is_monday * state.fav_num_monday +
        (1 - state.is_monday) * state.fav_num_not_monday);

    printf("\n2) Current favourite number is %d.",
        get_state(&state, "fav_num"));

    printf("\n3) Current favourite number is %d.",
        state_fav_num);

    printf("\n");

    return 0;
}
rwolst
  • 12,904
  • 16
  • 54
  • 75
  • You are not trying to shoehorn object orientated programming concepts into an essentially non-object-orientated language, are you? Did you consider C++? – Yunnosch Mar 02 '19 at 10:42
  • Why do you use maths and `int`s to get what could (and should) be done with a `bool` and an `if`? If you want something in one line you could at least use `? :` – alx - recommends codidact Mar 02 '19 at 10:44
  • 1
    You could use defines or an enum for the C properties, giving them each integer values, and store them in an array in your struct. You could then access the properties by using the appropriate named index. – Tom Karzes Mar 02 '19 at 10:52
  • Also, the Python code is poorly implemented. The return statement should be `return self.fav_num_monday if self.is_monday else self.fav_num_not_monday`. – Tom Karzes Mar 02 '19 at 10:54
  • You should use `int main(void)` instead of `int main()`. Read more here: https://stackoverflow.com/a/31263079/6872717 – alx - recommends codidact Mar 02 '19 at 11:25
  • You are not handling the case when the user inputs a string that doesn't match any of your expectations. – alx - recommends codidact Mar 02 '19 at 11:42
  • 1
    Use a "get function". If the compiler has visibility of the function definition, it can often be inlined e.g. defined `inline` in a header. There will be no performance difference between option 1 and option 2 IF the compiler inlines the function. Avoid using a macro since, at best, it has the same effect as option 1 and, at worst, does not respect scope and has unintended effects. Also, get the code working and clear. Only worry about performance if testing/profiling provides evidence of a need. Worrying too soon about performance, as you are, is called "premature optimisation" for a reason. – Peter Mar 02 '19 at 11:43

1 Answers1

2

You can get the best of both worlds (function and macro) for readability and performance, with a static inline function.

You usually wouldn't use that, but if you know the compiler is going to optimize its code away, then it's OK to use it. The usual rule I use is 3 or less lines of code, and the function should require extra performance.

That said, your get_state doesn't meet the (my) requirements for a static inline function, but if you only want a function to get only the fav_num, it would make sense:

struct State {
    int     fav_num_monday;
    int     fav_num_not_monday;
    bool    is_monday;
};


static inline   int get_fav_num(const struct State *state)
{

    if (state->is_monday)
        return  state->fav_num_monday;
    else
        return  state->fav_num_not_monday;
}


int main(void)
{
    struct State state;
    int fav_num;

    state   = (struct State){
        .fav_num_monday     = 12;
        .fav_num_not_monday = 5;
        .is_monday          = 1;
    };

    // Print favourite number in different ways.
    printf("\n");
    if (state.is_monday)
        fav_num = state->fav_num_monday;
    else
        fav_num = state->fav_num_not_monday;
    printf("1) Current favourite number is %d.\n", fav_num);

    fav_num = get_fav_num(&state);
    printf("4) Current favourite number is %d.\n", fav_num);

    return 0;
}

Disclaimer: This code needs C99 or later.

Although here the code is all together, the struct State {...}; and the static inline function would usually go in a header .h file.

Also, I would improve your get_state function in this way:

enum Properties {
    FAV_NUM_MONDAY,
    FAV_NUM_NOT_MONDAY,
    IS_MONDAY,
    FAV_NUM
};

int get_state(const struct State *state, int property)
{

    switch (property) {
    case FAV_NUM_MONDAY:
        return  state->fav_num_monday;
    case FAV_NUM_NOT_MONDAY:
        return  state->fav_num_not_monday;
    case IS_MONDAY:
        return  state->is_monday;
    case FAV_NUM:
        return  get_fav_num(state);
    default:
        return -1;  /* Error */
    }
}

This function would be a usual extern function and would go in a .c file, although the enum Properties should go in a header file so that it can be used by the user of the function.

Edit: Add high performance version using array

state.h

#include <stdint.h>

enum    State_Properties {
    FAV_NUM_MONDAY,
    FAV_NUM_NOT_MONDAY,
    IS_MONDAY,
    STATE_PROPERTIES
};


static inline
uint_fast8_t get_fav_num(const uint_fast8_t *restrict (state[STATE_PROPERTIES]))
{

    if ((*state)[IS_MONDAY])
        return  (*state)[FAV_NUM_MONDAY];
    else
        return  (*state)[FAV_NUM_NOT_MONDAY];
}

main.c

#include <inttypes.h>

#include "state.h"

int main(void)
{
    uint_fast8_t    state[STATE_PROPERTIES];
    uint_fast8_t    fav_num;
    uint_fast8_t    fav_num_monday;

    state   = (uint_fast8_t [STATE_PROPERTIES]){
        [FAV_NUM_MONDAY]        = 12;
        [FAV_NUM_NOT_MONDAY]    = 5;
        [IS_MONDAY]             = true;
    };

    // Print favourite number in different ways.
    fav_num = get_fav_num(&state);
    printf("5) Current favourite number is %"PRIuFAST8".\n", fav_num);

    // Example of how to retrieve any property:
    fav_num_monday  = state[FAV_NUM_MONDAY];
}

Of course you can change the type to anyone you want. I used uint_fast8_t, because your data can fit in there, and it is the fastest type on any system.

  • I would add only as a comment, that if all the types of the `struct` are the same, it could be better (for performance) to use an array instead, so that you wouldn't even need a function to access its elements, because an `enum` could be used to name its positions. – alx - recommends codidact Mar 02 '19 at 11:47
  • I think this is the best way. As it is known at compile time, am I right in thinking that a good compiler will avoid doing a linear search each time `get_state` is called, to find the matching property. If not, for a lot of porperties ths could become ineficient. – rwolst Mar 05 '19 at 13:42
  • If `get_state` is in the same .c file that it's caller, and the `property` is known at compile time, yes it will directly inline (copy) just `state->whatever;` instead of the function call. If `property` is unknown at compile time, it won't be possible to optimize. And if `property` is known at compile time, but the caller is in another file, I don't think it will be able to optimize, because that would be only known by the linker, and although I don't know much about the linker, I don't think it can do that kind of optimizations (please help here if anyone knows). – alx - recommends codidact Mar 05 '19 at 13:53
  • Anyway, I don't think the overhead is too much. If you still need to optimize that exact function for some reason (let's say you call it a million times outside of this file), you can also make this function `static inline` (and therefore move it to the .h file) and let the compiler decide. – alx - recommends codidact Mar 05 '19 at 13:53
  • In case you need the optimization, consider using the array I mention in the comment. That would avoid having a function at all. In this case, if the index is known at compile time, it will just load that position of the array instantly, and if it is known at run time, it will just add a very little overhead of doing some pointer arithmetics, but that's the fastest way it can be done. – alx - recommends codidact Mar 05 '19 at 13:55
  • Right, so if `get_state` is kept in the same header file as the `State` structure, then the compiler should just always inline the correct `case` statement (as there will be no linking necessary to call the function). – rwolst Mar 05 '19 at 14:14
  • Not always, just when it decides it is good. `inline` doesn't force the compiler, but instead it tells the compiler that it may be good to do it. – alx - recommends codidact Mar 05 '19 at 14:17
  • @rwoist Have a look at the edit I made. However, it can only be used if all the elements are of the same type, of course. – alx - recommends codidact Mar 05 '19 at 14:57