3
\$\begingroup\$

I wrote this small example piece of OOP being implemented in pure C. I wanted it to be reviewed for the following points:

  • Portability
  • Performance

    And especially

  • Usability
  • "How does it look"

Implementation:

typedef char    XCFString;

struct XCFStringStatic {
    void *(*initWithCString)(const char *);
};

struct XCFStringClass {
    void (*release)(void);

    size_t      length;
    char        *value;
};

// Pointer to current class
XCFString **XCFStringCTX    = NULL;

// Release method
void XCFString__release(void)
{
    assert(XCFStringCTX != NULL);
    XCFString *ctx  = *XCFStringCTX;

    free(((char *)ctx) - sizeof(struct XCFStringClass));
}

// Init method
void *XCFString__initWithCString(const char *str)
{
    size_t str_len              = strlen(str);
    struct XCFStringClass *data = malloc(sizeof(* data) + str_len + 1);

    if (!data) return NULL;

    char *str_val               = ((char *)data) + sizeof(* data);

    memcpy(str_val, str, str_len + 1);

    data->length                = str_len;
    data->value                 = str_val;
    data->release               = XCFString__release;

    return data->value;
}

struct XCFStringStatic XCFStringStatic = {.initWithCString = XCFString__initWithCString};

#define priv_getMacroName1(_0, _1, macroName, ...)          macroName
#define getMacroName1(macroName, args...)                   priv_getMacroName1(_0, ##args, macroName ## _1, macroName ## _0)(args)

#define XCFString_0()       (XCFStringStatic)
#define XCFString_1(_var)   (XCFStringCTX=&(_var), ((struct XCFStringClass *)(((char *)_var) - sizeof(struct XCFStringClass))))
// Switch between XCFString_0 and XCFString_1, GNUC only
#define XCFString(args...)                                  getMacroName1(XCFString, ##args)

Test code for main.c

int main(int argc, const char **argv)
{
    // Pointer to our String class
    XCFString   *string     = NULL;

    // Initialize string with "Hallo Welt!"
    string = XCFString().initWithCString("Hallo Welt!");

    // Print it as if it were a normal string
    fprintf(stderr, "String        : %s\n",     string);
    fprintf(stderr, "Length        : %zu\n",    strlen(string));
    // Get the properties from the string
    fprintf(stderr, "Str.value     : %s\n",     XCFString(string)->value);
    fprintf(stderr, "Str.length    : %zu\n",    XCFString(string)->length);

    // Release the string
    XCFString(string)->release();
}
\$\endgroup\$
  • \$\begingroup\$ Well on portability, clang says "named variadic macros are a GNU extension" and goes on to fail to build. \$\endgroup\$ – William Morris Oct 17 '14 at 16:47
2
\$\begingroup\$

I would actually avoid the attempt to mimic C++ syntax. It makes things more complex and exposes unnecessary details.

For me it would be equally object-oriented with the following C interface:

#ifdef __cplusplus
#extern "C" {
#endif

typedef struct XCFString_
{
    char unused__;
} XCFString;

XCFString* XCFString_Init();
XCFString* XCFString_InitFromCString(char const* sz);
size_t XCFString_Length(XCFString const* xcfs);
char const* XCFString_Value(XCFString const* xcfs);
void XCFString_Release(XCFString* xcfs);

#ifdef __cplusplus
}
#endif

The above interface hides the details while allowing polymorphism; you could e.g. keep the "vtable" implementation (but hidden) and provide different init-functions.

I really don't follow all that macro voodoo but having a mutable shared state - the "pointer to current class" - seems susceptible to race conditions.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.