easy struct inheritance & pseudo-polymorphism vs strict aliasing

浪子不回头ぞ 提交于 2019-11-30 20:40:17

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.

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).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!