简单的结构inheritance和伪多态与vs严格别名

如果有人回答我的问题,请不要告诉我使用C ++

所以,我在C中创建了一个使用面向对象方法的小型库。 我选择在C中使用两种主要的inheritance方法中较不常见的方法:将基类型的成员复制到派生类型的开头。 像这样的东西:

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

这种方法不如另一种方法(基类型的实例作为派生类型的第一个成员)受欢迎,因为

  1. 从技术上讲,没有标准保证基础和派生结构的第一个共同成员将具有相同的抵消。 但是,除了其中一个结构被打包而另一个结构没有被打包的情况之外,它们将在大多数(如果不是全部)已知编译器上具有相同的偏移量。
  2. 这种方法最严重的缺陷:它违反了严格的别名 。 将指向派生结构的指针转换为其基类型然后解除引用指针在技术上是未定义的行为。

但是,与其他方法相比,它也有其优点:

  1. 更少详细:访问已inheritance的派生结构的成员与访问尚未inheritance的结构相同,而不是转换为基类型, 然后访问所需的成员;
  2. 这实际上是真正的inheritance而不是构成 ;
  3. 虽然可能需要一些预处理器滥用,但它与其他方法一样容易实现;
  4. 我们可以得到一个实际多重inheritance的半生不熟的forms,我们可以从几个基类型inheritance,但只能转换为其中一个。

我一直在寻找使我的库编译和使用强制执行严格别名(如gcc )的编译器正确工作的可能性,而无需用户手动关闭它。 以下是我研究过的可能性:

  1. 工会。 遗憾的是,由于以下几个原因,这些是禁忌:

    1. 详细程度回归! 要遵循通过联合访问2个结构的第一个共同成员的标准规则,必须(从C99开始)明确使用联合来访问第一个共同成员。 我们需要特殊的语法来访问联合中每种类型的成员!
    2. 空间。 考虑一个inheritance层次结构。 我们有一个类型,我们希望能够从每个派生类型转换为。 我们希望为每种类型做到这一点。 我看到唯一可行的联合使用解决方案是整个层次结构的并集,它必须用于将派生类型的实例转换为基类型。 它必须与整个层次结构中派生类型最多的一样大……
  2. 使用memcpy而不是直接解除引用(如此处 )。 这看起来是个不错的解决方案。 但是,函数调用会产生开销,是的,再一次,冗长。 据我所知, memcpy作用也可以通过将指向struct的指针转换为指向char的指针然后解除引用来手动完成,如下所示: (member_type)(*((char*)(&struct_pointer->member))) = new_value; 嘎,再次冗长。 好吧,这可以用宏包裹。 但是,如果我们将指针转换为指向不兼容类型的指针, 然后将其转换为char*并取消引用它, 那么它仍然可以工作吗? 像这样: (member_type)(*((char*)(&((struct incompatible_type*)struct_pointer)->member))) = new_value;

  3. 声明我们将要转换为volatile所有类型实例。 我想知道为什么这不经常出现。 正如我所理解的那样, volatile用来告诉编译器指针指向的内存可能会意外地发生变化,从而根据一段指向内存不会改变的假设取消优化,这是导致所有严格别名问题。 当然,这仍然是未定义的行为; 但对于某些类型的某些实例的“hackishly”禁用严格的别名优化,它不是一个可行的跨平台解决方案吗?

除了上面的问题,还有两个:

  1. 我上面说的是错误的吗?
  2. 我错过了一些可以帮助我的事情吗?

我不认为你对通过char*投射的想法是有效的。 规则是:

对象的存储值只能由具有以下类型之一的左值表达式访问

表达式的子表达式是兼容的,但整体表达式不兼容。

我认为唯一现实的方法是构图:

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

我意识到这是一种在理智上没有吸引力的方法来实现inheritance。

PS:我没有把你的虚拟成员函数指针放在我的派生类中。 它需要从base访问,因此需要在那里声明(假设它是basederived都存在的多态函数)。 我还添加了一个this参数来充实模型。

memcpy应该是要走的路。 不要担心函数调用开销。 通常情况下,没有。 memcpy通常是编译器内在的,这意味着编译器应该为它编写最有效的代码,并且它应该知道它可以优化memcpies的位置。

不要将指针转换为不兼容的指针然后解除引用。 这是一条未定义行为的道路。

如果您接受表达式语句和gcc的##__VA_ARGS__ ,您可以拥有一个MC_base_method(BaseType,BaseMethod,Derived_ptr,...)宏,它BaseMethod使用Derived_ptr...正确调用BaseMethod ,只要您可以使用副本一个结构,好像它是原始的(例如,没有指向结构自己的成员的指针)。

以下是一些支持OOP的宏糖的示例:

 //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  #include  //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=%db=%dc=%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); } 

我认为编译器的工作是优化memcpies。 但是,如果它没有,你的结构很大,你就搞砸了。 如果你的结构包含指向它们自己的成员的指针(例如,如果你不能使用每字节副本一个字节,就好像它是原始的那样)。