为了拥有一个干净的代码,使用一些OO概念可能是有用的,即使在C中。我经常编写由一对.h和.c文件组成的模块。 问题是模块的用户必须小心,因为C中不存在私有成员。使用pimpl习语或抽象数据类型是可以的,但是它添加了一些代码和/或文件,并且需要更重的代码。 我讨厌在不需要时使用访问器。
这是一个想法,它提供了一种方法,使编译器抱怨对“私有”成员的无效访问,只需要几个额外的代码。 我们的想法是定义两次相同的结构,但为模块的用户添加了一些额外的“const”。
当然,演员也可以写“私人”成员。 但重点是避免模块用户的错误,而不是安全地保护内存。
/*** 2DPoint.h module interface ***/ #ifndef H_2D_POINT #define H_2D_POINT /* 2D_POINT_IMPL need to be defined in implementation files before #include */ #ifdef 2D_POINT_IMPL #define _cst_ #else #define _cst_ const #endif typedef struct 2DPoint { /* public members: read and write for user */ int x; /* private members: read only for user */ _cst_ int y; } 2DPoint; 2DPoint *new_2dPoint(void); void delete_2dPoint(2DPoint **pt); void set_y(2DPoint *pt, int newVal); /*** 2dPoint.c module implementation ***/ #define 2D_POINT_IMPL #include "2dPoint.h" #include #include 2DPoint *new_2dPoint(void) { 2DPoint *pt = malloc(sizeof(2DPoint)); pt->x = 42; pt->y = 666; return pt; } void delete_2dPoint(2DPoint **pt) { free(*pt); *pt = NULL; } void set_y(2DPoint *pt, int newVal) { pt->y = newVal; } #endif /* H_2D_POINT */ /*** main.c user's file ***/ #include "2dPoint.h" #include #include int main(void) { 2DPoint *pt = new_2dPoint(); pt->x = 10; /* ok */ pt->y = 20; /* Invalid access, y is "private" */ set_y(pt, 30); /* accessor needed */ printf("pt.x = %d, pt.y = %dn", pt->x, pt->y); /* no accessor needed for reading "private" members */ delete_2dPoint(&pt); return EXIT_SUCCESS; }
现在,问题是:这个技巧可以用C标准吗? 它适用于GCC,编译器不会抱怨任何东西,即使有一些严格的标志,但我怎么能确定这是真的好吗?
这违反了C 2011 6.2.7 1。
6.2.7 1要求不同翻译单元中相同结构的两个定义具有兼容类型。 不允许将const
在一个而不是另一个中。
在一个模块中,您可以引用其中一个对象,并且成员看起来是编译器的const。 当编译器将调用写入其他模块中的函数时,它可以保存来自寄存器或其他缓存中的const成员的值,或者来自源代码中的部分或完全计算表达式的值,而不是函数调用。 然后,当函数修改成员并返回时,原始模块将不具有更改的值。 更糟糕的是,它可能会使用更改后的值和旧值的某种组合。
这是非常不正确的编程。
这几乎肯定是未定义的行为。
写入/修改声明为const
的对象是禁止的,这样做会导致UB。 此外,您采用的方法将struct 2DPoint
重新声明为两种技术上不同的类型,这也是不允许的。
请注意,这(通常是未定义的行为)并不意味着它“肯定不会工作”或“它必须崩溃”。 事实上,我发现它的工作原理是合乎逻辑的,因为如果一个人聪明地读取源,他可能很容易找出它的目的是什么以及为什么它被认为是正确的。 然而,编译器并不是智能的 – 充其量,它是一个有限的自动机,它不知道代码应该做什么; 它只服从(或多或少)语法的句法和语义规则。
在Bjarne Stroustrup的话中:C不是为支持 OOP而设计的,虽然它支持 OOP,这意味着可以用C编写OOP程序,但只是很难这样做。 因此,如果您必须在C中编写OOP代码,使用此方法似乎没有任何问题,但最好使用更适合此目的的语言。
通过尝试在C中编写OOP代码,您已经进入了必须覆盖“常识”的区域,因此只要您负责正确使用它,这种方法就可以了。 您还需要确保对其进行全面而严格的记录,并且每个与代码有关的人都会意识到这一点。
编辑哦,你可能不得不使用一个演员绕过const
。 我不记得是否可以像C ++ const_cast
一样使用C风格的const_cast
。
您可以使用不同的方法 – 声明两个struct
,一个用于没有私有成员的用户(在标头中),另一个用于私有成员,供您在实现单元中内部使用。 所有私人成员都应该放在公共成员之后。
您总是传递指向struct
的指针并在需要时将其struct
为内部使用,如下所示:
/* user code */ struct foo { int public; }; int bar(void) { struct foo *foo = new_foo(); foo->public = 10; } /* implementation */ struct foo_internal { int public; int private; }; struct foo *new_foo(void) { struct foo_internal *foo == malloc(sizeof(*foo)); foo->public = 1; foo->private = 2; return (struct foo*)foo; // to suppress warning }
C11允许未命名的结构字段 (GCC支持它一段时间),因此在使用GCC(或符合C11的编译器)的情况下,您可以将内部结构声明为:
struct foo_internal { struct foo; int private; };
因此,不需要额外的努力来保持结构定义同步。
需要了解更多c/c++开发分享C中的“私有”结构成员与const,也可以关注C/ C++技术分享栏目—计算机技术网(www.ctvol.com)!
以上就是c/c++开发分享C中的“私有”结构成员与const相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/979206.html