用C预处理器模拟继承

用C预处理器模拟继承,c,inheritance,c-preprocessor,C,Inheritance,C Preprocessor,我有一个关于如何在C中实现接口的想法。这个想法非常简单,但是在搜索讨论这个问题时,我没有看到提到它 因此,C中的“标准”继承模拟方法本质上是 struct dad { arm_t right_arm; arm_t left_arm; } struct kid { struct dad parent; diaper_t diaper; } 我觉得这很难看,因为孩子。父母。右臂比孩子。右臂更有意义。如果您不介意使用gcc独占标志,显然还有另一种方法,类似于 struct

我有一个关于如何在C中实现接口的想法。这个想法非常简单,但是在搜索讨论这个问题时,我没有看到提到它

因此,C中的“标准”继承模拟方法本质上是

struct dad {
   arm_t right_arm;
   arm_t left_arm;
}

struct kid {
   struct dad parent;
   diaper_t diaper;
}
我觉得这很难看,因为孩子。父母。右臂比孩子。右臂更有意义。如果您不介意使用gcc独占标志,显然还有另一种方法,类似于

struct kid {
    struct dad;
    diaper_t diaper;
}
但这不是便携式的。然而,可移植的是

// file: dad.interface
arm_t right_arm;
arm_t left_arm;

// file: kid.h
struct kid {
    #include dad.interface
    diaper_t diaper;
}

就这个问题而言,我对替代方案的建议不感兴趣。我的问题很简单:这种方法有什么问题吗?

您可以这样做:

#define myclass_members int a;\
                void (*f)(void)

struct myclass {
    myclass_members;
};

#define derived_cls_members myclass_members;\
                            double d;\
                            void (*g)(void)

struct derived_cls {
    derived_cls_members;
};
如果您感兴趣,可以在此基础上检查整个框架

编辑:事实证明这种方法可能有效,但正如其他人指出的那样,它是可疑的。更好的选择是使用c11中添加的匿名结构扩展,正如@tgregory所指出的,它仍然可以与预处理器一起使用

#define myclass_members struct { int a; void (*f)(void); }
编辑2:进一步研究表明,第一种方法(只有预处理器)实际上应该可以工作。因此,如果您不关心注释分配问题中提到的问题,或者使用memset()/memcpy()等函数可能会出现的问题,那么应该可以

资料来源:

编辑3:如果使用优化进行编译,则要使用-fno严格别名标志进行编译,以避免优化


一句话:它是肮脏的,是一种黑客行为,但应该在实践中起作用。

既然您在继承的上下文中提出了这个问题,我想一个更完整(正确)的例子是:

文件:dad.interface 归档dad.h 档案:kid.h 这方面有几个问题,其中包括:

  • 这并不是真正的继承——它只是两个碰巧有匹配初始成员的结构
    struct kid
    struct dad
    没有其他关系,前者当然不会从后者继承任何东西。从
    struct kid
    s的角度来看,甚至不清楚是否存在
    struct dad

  • 虽然您没有使用这个术语,但是人们通常(尽管没有根据)认为多态性与继承密切相关。你的方法无法做到这一点。尽管共享一个共同的成员初始序列,但这两种结构类型是不兼容的,并且指向这些类型的指针是不兼容的,因为标准使用了这个术语。特别是,通过类型为
    struct dad*
    的指针访问
    struct kid
    ,违反了严格的别名规则,因此会产生未定义的行为。(当
    struct kid
    struct dad
    嵌入为其第一个成员时,这一点不适用。)

  • 该方法的可扩展性较差。例如,假设要添加从
    struct kid
    继承的
    struct-son
    struct-kid
    。现在,您还必须将
    struct kid
    的成员拖出到它自己单独的头中。我想你可以先发制人,如果你能预测任何人可能想要继承的结构类型,或者如果你只是对每一种结构类型这样做。哎呀,真是一团糟


  • 这不适合,因为我们这里的目的是解决具体问题,而不是讨论如何将方形销钉推入圆孔。对你的问题的简短回答是,Obj-C、C++和C是为了这个目的而开发的。幸运的是,C语言在这些“增强”方面一直处于孤立状态。软件开发技能之一就是为特定任务选择正确的工具。没有人可以禁止你用螺丝刀清洁耳朵,但如果你在这方面的广泛经验和发现不会受到社区的欢迎,也不要感到惊讶。就这么简单。好吧,在大多数面向对象语言中,继承比这更重要。无论如何,您可以通过嵌入基“类”的匿名结构实例(假设是C11或一个像样的编译器)来更干净地实现相同的效果。更好的方法是将其与显式父对象一起封装在匿名联合中,您可以传递直接的父对象指针而无需强制转换允许在从子对象访问父字段时省略
    dad
    成员,同时根据需要转发父对象。一个完全合理的有用的小骗子,虽然命名它继承可能是一个不必要的分心。“Kevdimm很确信这个问题有很好的机会被回答,大多数人都会认为“客观”。再次强调,我不是在问如何将方形木钉塞进圆孔,我是在问为什么木钉首先被认为是方形的。是的,这是另一种选择,尽管#include“super.interface”让意图更加清晰,而且你不必使用恼人的反斜杠:)是的,但是你会有一种include文件的语气来管理。我发现公约更容易处理。整洁,但包括技巧。正如@JohnBollinger提到的,填充将是一个问题。对gcc和std=c11的快速检查表明,小小的更改可能有助于定义myclass#u成员结构{int a;void(*f)(void);}@tgregory是的,我考虑过填充问题,但根据我的经验,当结构与单词对齐时,一切都会就绪。在某些地方,在某些实现中,它很可能不起作用,idk。匿名结构扩展是否以某种方式确保对齐?@VladDinev是的。在linux x64 gcc
    struct parent_s{parent;}上测试
    struct child{parent;int d;}
    。如果
    #定义父int a;双b;int c
    sizeof(父项)=24
    sizeof(子项)=24
    偏移量(子项,d)=20
    
    arm_t right_arm;
    arm_t left_arm;
    
    struct dad {
        #include "dad.interface"
    };
    
    struct kid {
        #include "dad.interface"
        diaper_t diaper;
    };