implement_ex
宏是 Arm-2D 库中用于面向对象编程(OOP)支持的核心宏定义。
implement_ex
宏的定义和作用
implement_ex
宏在 Library/Include/arm_2d_utils.h
中定义,用于在 C 语言中实现类似继承的功能:
/*!* \note do NOT use this macro directly*/
#ifdef __cplusplus
# define __implement_ex(__type, __name) __type __name
#else
# define __implement_ex(__type, __name) \union { \__type __name; \__type; \}
#endif
/*!* \note do NOT use this macro directly*/
#define __implement(__type) __implement_ex( __type, \
/*!* \brief inherit a given class* \param __type the base class, you can use .use_as_xxxxx for referencing * the base.* \note this macro supports microsoft extensions (-fms-extensions)*/
#define implement(__type) __implement(__type)/*!* \brief inherit a given class and give it an alias name* \param __type the base class* \param __name an alias name for referencing the base class* \note this macro supports microsoft extensions (-fms-extensions)*/
#define implement_ex(__type, __name) __implement_ex(__type, __name)
该宏的具体实现根据编译器环境有所不同:
- C++ 环境下:直接定义为普通的结构体成员
- C 环境下:使用匿名联合体来实现成员访问的灵活性
实际使用示例
在 Arm-2D 的数据结构中,implement_ex
被广泛使用。例如在 arm_2d_region_t
结构体中:
/*!* \brief a type for an rectangular area**/
typedef struct arm_2d_region_t {implement_ex(arm_2d_location_t, tLocation); //!< the location (top-left corner)implement_ex(arm_2d_size_t, tSize); //!< the size
} arm_2d_region_t;
这里 implement_ex(arm_2d_location_t, tLocation) 表示该结构体继承自基类 arm_2d_location_t,可以通过 tLocation来访问这个基类的成员
与其他相关宏的关系
implement_ex
是一系列 OOP 支持宏的一部分:
implement(__type)
- 不指定别名的继承implement_ex(__type, __name)
- 指定别名的继承inherit(__type)
和inherit_ex(__type, __name)
- 不支持 Microsoft 扩展(-fms-extensions)的版本
inherit主要作用是:当基类里的成员名称与派生类中的成员名称,或者进行多继承的时候,多个基类里的成员名称存在冲突时,就要使用 inherit 来避免问题。如果不存在冲突时,应该尽可能使用 implement和implement_ex
了解如上的作用和定义后,要完全理解它的功能,我们需要两个基本知识:
匿名结构和联合体的定义和使用
在C语言中,可以定义匿名结构体或联合体,这通常用于创建复杂的数据类型,而不需要为每个单独的成员定义一个名称。
匿名结构体和联合体通常用于以下场景:
-
简化代码:当不需要为结构体或联合体的每个成员单独命名时,可以使用匿名类型来简化代码。
-
嵌套类型:在定义更复杂的数据结构时,可以使用匿名类型来避免重复定义相同的结构体或联合体。
-
接口定义:在定义函数接口时,可以使用匿名类型来定义参数或返回值的结构
正常结构体定义(联合体union类似,不再赘述):
struct tagVar{int a;float b;
} var1;
匿名结构体定义(联合体union类似,不再赘述):
struct {int a;float b;
} var1;
两者唯一的区别就在前者多了tagVar名字,我们可以使用如下方式再次定义其他结构变量:
struct tagVar Var2;
而后者由于没有具体的名称,所以我们无法再次用它去定义其他变量。
Microsoft 扩展(-fms-extensions)
Microsoft 扩展的作用是对匿名结构体(或者联合体)的进一步展开支持,为了理解它的作用,我们以arm2d中的一个数据结构定义arm_2d_region_t 的定义为例来说明其作用:
该结构体定义如下:
/*!* \brief a type for an rectangular area**/
typedef struct arm_2d_region_t {implement_ex(arm_2d_location_t, tLocation); //!< the location (top-left corner)implement_ex(arm_2d_size_t, tSize); //!< the size
} arm_2d_region_t;
我们将宏implement_ex展开,得到如下代码:
typedef struct arm_2d_region_t { // 展开 implement_ex(arm_2d_location_t, tLocation) union { arm_2d_location_t tLocation; //!< the location (top-left corner) arm_2d_location_t; //!< 匿名成员 }; // 展开 implement_ex(arm_2d_size_t, tSize) union { arm_2d_size_t tSize; //!< the size arm_2d_size_t; //!< 匿名成员};
} arm_2d_region_t;
对于以上代码,我们就可以采用如下方式进行成员访问:
arm_2d_region_t region; // 通过命名成员访问
region.tLocation.iX = 10;
region.tLocation.iY = 20;
region.tSize.iWidth = 100;
region.tSize.iHeight = 50;
这里就引出一个疑问:展开后的匿名结构体arm_2d_location_t和arm_2d_size_t有什么作用呢?是不是很奇怪?
其实,我们本意是希望这个定义完全展开后,形如下面的代码(将匿名结构体也展开):
typedef struct arm_2d_region_t { // tLocation 成员 union { struct { int16_t iX; //!< x in Cartesian coordinate system int16_t iY; //!< y in Cartesian coordinate system } tLocation; struct { int16_t iX; int16_t iY; }; }; // tSize 成员 union { struct { int16_t iWidth; //!< width of an rectangular area int16_t iHeight; //!< height of an rectangular area } tSize; struct { int16_t iWidth; int16_t iHeight; }; };
} arm_2d_region_t;
那么,我们就可以方便的用如下两种方式来访问成员变量:
arm_2d_region_t region; // 通过命名成员访问
region.tLocation.iX = 10;
region.tLocation.iY = 20;
region.tSize.iWidth = 100;
region.tSize.iHeight = 50; // 通过匿名成员直接访问
region.iX = 10;
region.iY = 20;
region.iWidth = 100;
region.iHeight = 50;
是不是后面一种方式更简洁和直观?如此一来,写代码会清爽很多了。
要达到上述目的,让编译器将匿名结构体也展开,我们就需要使用-fms-extensions编译选项,明确指示编译器完成这个工作,对匿名结构体进行展开。
关于-fms-extensions
-fms-extensions 是 GCC(GNU Compiler Collection)和 Clang 编译器的一个编译选项,它用于启用对 Microsoft 编译器(MSVC)特定扩展的支持。这些扩展包括:
- 允许 Microsoft 版本的匿名联合和结构体,这包括对 C11 匿名联合和结构体的支持,以及 Microsoft 特定的变体,如完全省略大括号成员列表,并将成员放置在父命名空间中,即使结构体/联合体有标识符。
- 在 C++ 中,允许类成员与其类型同名(例如 using foo = int; struct A { foo foo; })。当禁用 ms-extensions 时,这种行为在 C 中是合法的;或者在没有给出 -pedantic 标志的 extern “C” 块中是合法的。对于这种情况的错误消息是 declaration of NAME changes meaning of NAME。
- 在 C++ 中,允许隐式 int;任何会产生 ISO C++ forbids declaration of NAME with no type 诊断的情况现在都被允许,假设类型为 int。例如:const *p; 或 const f();。
- 在 C++ 中,允许从命名一个非静态成员函数的 qualified-id 隐式转换为指向成员的指针。在 ISO C++ 中,执行该转换需要 & 运算符。
- 在 C++ 中,如果 f(一个未限定的标识符)在该上下文中命名一个非重载的成员函数,则允许 &f 形成指向成员的指针。ISO C++ 要求使用类名进行明确的限定。
文章原创,欢迎转载,请注明出处,未经书面允许,不得用于商业用途。