如果有人回答我的问题,请不要告诉我使用C ++ 。
所以,我在C中创建了一个使用面向对象方法的小型库。我选择在C中使用两种主要的继承方法中较不常见的方法:将基类型的成员复制到派生类型的开头。像这样:
struct base {
int a;
int b;
char c;
};
struct derived {
int a;
int b;
char c;
unsigned int d;
void (*virtual_method)(int, char);
};
这种方法不如另一种方法(基类型的实例作为派生类型的第一个成员)受欢迎,因为
然而,与其他方法相比,它也有其优点:
我一直在寻找使我的库编译和使用强制执行严格别名的编译器(如 gcc )正确工作的可能性,而无需用户手动关闭它。以下是我研究过的可能性:
联盟。遗憾的是,由于以下几个原因,这些是禁忌的:
使用memcpy
代替直接解除引用(例如here)。这看起来是个不错的解决方案。但是,函数调用会产生开销,是的,再一次,冗长。据我所知,memcpy
所做的事情也可以通过将指向结构的指针强制转换为指向char
的指针,然后取消引用它来完成,如下所示:(member_type)(*((char*)(&struct_pointer->member))) = new_value;
Gah,再次详述。好吧,这可以用宏包裹。但是,如果我们将指针转换为指向不兼容类型的指针,然后然后将其转换为char*
并取消引用它,那么它仍然有用吗?像这样:(member_type)(*((char*)(&((struct incompatible_type*)struct_pointer)->member))) = new_value;
声明我们要转换为volatile
的所有类型实例。我想知道为什么这不经常出现。据我所知,volatile
用于告诉编译器指针指向的内存可能会意外更改,从而根据一段指向内存不会改变的假设取消优化,是所有严格别名问题的原因。当然,这仍然是未定义的行为;但对于某些类型的某些实例的“hackishly”禁用严格的别名优化,它不是一个可行的跨平台解决方案吗?
除上述问题外,还有两个问题:
答案 0 :(得分:4)
我不认为您通过char*
投射的想法是有效的。
规则是:
对象的存储值只能由左值访问 具有以下类型之一的表达式
表达式的子表达式是兼容的,但整体表达式不兼容。
我认为唯一现实的方法是构图:
struct base {
int a;
int b;
char c;
void (*virtual_method)(base*/*this*/,int, char);
};
struct derived {
struct base;
unsigned int d;
};
我意识到这是一种在理智上没有吸引力的方式来实现继承。
PS:我没有把你的虚拟成员函数指针放在我的派生类中。它需要从base
访问,因此需要在那里声明(假设它是base
和derived
都存在的多态函数)。
我还添加了一个this
参数来充实模型。
答案 1 :(得分:1)
memcpy
应该是要走的路。
不要担心函数调用开销。通常情况下,没有。 memcpy
通常是编译器内在的,这意味着编译器应该为它编写最有效的代码,并且它应该知道它可以优化memcpies的位置。
不要将指针转换为不兼容的指针然后取消引用。这是走向未定义行为的道路。
如果您接受表达式语句和gcc的##__VA_ARGS__
,那么您可以使用MC_base_method(BaseType,BaseMethod,Derived_ptr,...)
宏来正确调用BaseMethod
Derived_ptr
和...
因为你可以使用结构的副本,就像它是原始的一样(例如,没有指向结构自己的成员的指针)。
这是一个支持一些额外支持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 <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);
}
我认为编译器的工作是优化memcpies。但是,如果它没有,你的结构是巨大的,那你就搞砸了。如果你的结构包含指向它们自己的成员的指针(例如,如果你不能使用每字节副本的字节,就好像它是原始的那样)。