easy struct inheritance & pseudo-polymorphism vs strict aliasing

前端 未结 2 2036
一向
一向 2021-01-05 14:00

If anybody answers my question, please don\'t tell me to use C++.

So, I\'m making a small library in C that uses an object-oriented approach. I chose to use

相关标签:
2条回答
  • 2021-01-05 14:09

    memcpy should be the way to go. Don't worry about function call overhead. Most often than not, there's none. memcpy is usually a compiler intrinsic, which means the compiler should inline the most efficient possible code for it, and it should know where it can optimize memcpies out.

    Don't cast pointers to incompatible pointers and then dereference. That's a road towards undefined behavior.

    If you accept expression statements and gcc's ##__VA_ARGS__, you could have a MC_base_method(BaseType,BaseMethod,Derived_ptr,...) macro that calls a BaseMethod with Derived_ptr and ... correctly, as long as you can work with a copy of a struct as if it was the original (e.g., no pointers to the struct's own members).

    Here's an example with some additional OOP-supporting macro sugar:

    //Helper macros for some C++-like OOP in plain C 
    #define MC_t_alias(Alias, ...)  typedef __VA_ARGS__ Alias               //like C++'s  using 
    #define Struct(Nm,...) MC_t_alias(Nm, struct Nm); struct Nm __VA_ARGS__ //autypedefed structs
    
    #define ro const //readonly -- I don't like the word const
    
    //Helper macros for method declarations following my 
    //Type__method(Type* X, ...) naming convention
    #define MC_mro(Tp,Meth, ...) Tp##__##Meth(Tp ro*X, ##__VA_ARGS__)
    
    #include <stdio.h>
    #include <string.h>
    //I apend my data structs with _d to know they're data structs
    Struct(base_d, {
      int a;
      int b;
      char c;
    });
    
    Struct(derived_d, {
      int a;
      int b;
      char c;
      unsigned int d;
      void (*virtual_method)(derived_d*, int, char);
    });
    
    //print method is unaware of derived_d 
    //it takes a `base_d const *X` (the mro (method, readonly) macros hides that argument (X==`this` in common OOP speak))
    int MC_mro(base_d,print) 
    {
        return printf("{ a=%d b=%d c=%d }", X->a, X->b, X->c);
    }
    
    /*
        Call a (nonvirtual) base method 
    */
    
    #define MC_base_method(BaseType, Method, Derived_p, ...)                       \
    ({                                                                             \
        int _r; /*if you conventionally return ints*/                                \
                /*otherwise you'll need __typeof__ to get the type*/               \
        BaseType _b;                                                               \
        memcpy(&_b, Derived_p, sizeof(_b));                                        \
        _r = BaseType##__##Method(&_b, ##__VA_ARGS__);                             \
        /*sync back -- for non-readonly methods */                                 \
        /*a smart compiler might be able to get rid of this for ro method calls*/  \
        memcpy(Derived_p, &_b, sizeof(_b));                                        \
        _r;                                                                        \
    })
    
    
    int main()
    {
        derived_d d = {1,2,3,4};
        MC_base_method(base_d, print, &d);
    }
    

    I consider it the compilers job to optimize the memcpies out. However, if it doesn't and your structs are huge, you're screwed. Same if your structs contain pointers to their own members (i.e., if you can't work with a byte per byte copy as if it was the original).

    0 讨论(0)
  • 2021-01-05 14:24

    I don't think your idea about casting via char* is valid. The rule is:

    An object shall have its stored value accessed only by an lvalue expression that has one of the following types

    A sub-expression of your expression is compatible but the overall expression isn't compatible.

    I think the only realistic approach is composition:

    struct base {
      int a;
      int b;
      char c;
    
      void (*virtual_method)(base*/*this*/,int, char);
    
    };
    
    struct derived {
        struct base;
        unsigned int d;
    };
    

    I realize that's an intellectually unappealing way to achieve inheritance.

    PS: I haven't put your virtual member function pointer in my derived class. It needs to be accessible from base so needs to be declared there (assuming it's a polymorphic function that exists for both base and derived). I've also added a this parameter to flesh out the model a touch.

    0 讨论(0)
提交回复
热议问题