Is it a good idea to use varargs in a C API to set key value pairs?

前端 未结 7 2551
情书的邮戳
情书的邮戳 2021-02-20 11:47

I am writing an API that updates a LOT of different fields in a structure.

I could help the addition of future fields by making the update function variadic:

<         


        
7条回答
  •  不要未来只要你来
    2021-02-20 12:37

    I tend to avoid varargs except in one specific circumstance where it's very useful. Variable arguments don't really deliver all that much benefit over and above what can be done by individual function calls, especially in your case.

    In terms of readability (and that's usually what I prefer over raw speed except for very specific cases), there's no real difference between the following two options (I've added a count to the varargs versions since you need either a count or sentinel to detect the end of the data):

    update(2, FIELD_NAME1, 10, FIELD_NAME2, 20);
    update(3, FIELD_NAME3, 10, FIELD_NAME4, 20, FIELD_NAME5, 30);
    /* ========== */
    update(FIELD_NAME1, 10);
    update(FIELD_NAME2, 20);
    update(FIELD_NAME3, 10);
    update(FIELD_NAME4, 20);
    update(FIELD_NAME5, 30);
    

    In fact, as the varargs version gets longer, you'll need to split it up anyway, for formatting:

    update(5,
        FIELD_NAME1, 10,
        FIELD_NAME2, 20,
        FIELD_NAME3, 10,
        FIELD_NAME4, 20,
        FIELD_NAME5, 30);
    

    Doing it the "one call per field name" way results in simpler code in the function itself, and does not degrade the readability of the calls. In addition, it allows the compiler to properly detect certain errors that it cannot do for varargs, such as incorrect types or a mismatch between the user-supplied count and the actual count.

    If you really must be able to call a single function to do it, I'd opt for:

    void update (char *k1, int v1) {
        ...
    }
    void update2 (char *k1, int v1, char *k2, int v2) {
        update (k1, v1);
        update (k2, v2);
    }
    void update3 (char *k1, int v1, char *k2, int v2, char *k3, int v3) {
        update (k1, v1); /* or these two could be a single */
        update (k2, v2); /*   update2 (k1, v1, k2, v2);    */
        update (k3, v3);
    }
    /* and so on. */
    

    You could even do the higher-level functions as macros if you prefer, without losing type safety.

    The only place I tend to use varargs functions is when providing the same functionality as printf() - for example, I've occasionally had to write logging libraries with functions such as logPrintf() providing the same functionality. I can't think of any other time in my long (and I mean, long :-) time at the coalface that I've needed to use it.

    As an aside, if you do decide to use varargs, I tend to go for sentinels rather than counts since this prevents mismatches when adding fields. You could easily forget to adjust the count and end up with:

    update (2, k1, v1, k2, v2, k3, v3);
    

    when adding, which is insidious because it silently skips k3/v3, or:

    update (3, k1, v1, k2, v2);
    

    when deleting, which is almost certainly fatal to the successful running of your program.

    Having a sentinel prevents this (as long as you don't forget the sentinel, of course):

    update (k1, v1, k2, v2, k3, v3, NULL);
    

提交回复
热议问题