可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In C++ and Java, data structures can have private
, public
and protected
regions. I'd like to port this concept to a C language program I am writing.
Are there any idioms for implementing private or protected function pointers and data fields in a C struct
?
I know that C struct
s are public, I'm looking for an idiom to help hide some implementation details and force users to use the public interface.
Note: The language has been chosen by the shop, so I am stuck implementing Object Oriented concepts into C.
Thanks.
回答1:
As you know, you cannot do this. However, there are idioms that will allow a similar effect.
C will allow you do do something similar to what is known as the "pimpl" idiom in object-oriented design. Your struct can have an opaque pointer to another forward-declared struct that acts as the struct's private data. Functions that operate on the struct, taking the place of member functions, can have the full definition for the private member, and can make use of it, while other parts of the code cannot. For example:
In a header, foo.h:
struct FooPrivate;
struct Foo {
/* public: */
int x;
double y;
/* private: */
struct FooPrivate* p;
};
extern struct Foo* Foo_Create(); /* "constructor" */
extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */
In the implementation, foo.c:
struct FooPrivate {
int z;
};
struct Foo* Foo_Create()
{
struct Foo* foo = malloc(sizeof(Foo));
foo->p = malloc(sizeof(FooPrivate));
foo->x = 0;
foo->y = 0;
foo->p->z = 0;
return foo;
}
void Foo_DoWhatever(struct Foo* foo)
{
foo->p->z = 4; /* Can access "private" parts of foo */
}
In a program:
#include "foo.h"
int main()
{
struct Foo* foo = Foo_Create();
foo->x = 100; /* Can access "public" parts of foo */
foo->p->z = 20; /* Error! FooPrivate is not fully declared here! */
Foo_DoWhatever(foo); /* Can call "member" function */
return 0;
}
Note the need to use a "constructor" function in order to allocate memory for the private data. Obviously you would need to pair this with a special "destructor" function in order to deallocate the private data properly.
Or, alternatively, if you would like your struct to have no public fields whatsoever, you could make the entire struct opaque, and just have the header be something like
struct Foo;
extern struct Foo* Foo_Create(); /* "constructor" */
extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */
With the actual definition of struct Foo
in foo.c, and getter and setter functions available for any properties you would like to provide direct access to.
回答2:
The concept sometimes used in C is
// lib.h
typedef struct {
int publicInt;
//...
char * publicStr;
} Public;
Public * getPublic();
int function(Public * public);
// lib.c
typedef struct {
Public public;
int privateInt;
// ...
char * privateStr
} Private;
static Private * getPrivate();
Public * getPublic() { return (Public*) getPrivate(); }
int function(Public * public) {
Private * private = (Private *) public;
// ...
}
This uses the standard trick that a pointer to a struct can be interchanged with a pointer to the first element in a struct.
If you want all your fields to be private, it's even easier:
// lib2.h
typedef struct AllPrivate * Handle;
Handle getHandle();
int function2(Handle handle);
// lib2.c
struct AllPrivate { /* ... */ }
Files that #include
lib2.h won't complain, since we only use struct AllPrivate *
, and all pointers are the same size, so the compiler doesn't need to know the innards of struct AllPrivate
.
To do a protected region, you'd just have to define
// include/public.h
struct Public { /* ... */ }
struct Public * getPublic();
int somePublicFunction(struct Public *);
// dev/include/protected.h
struct Protected { struct Public public; /* ... */ }
struct Protected * getProtected();
int someProtectedFunction(struct Protected *);
// dev/src/private.c
struct Private { struct Protected protected; /* ... * /}
struct Public * getPublic() { return (struct Public *) getPrivate(); }
struct Public * getProtected() { return (struct Protected *) getPrivate(); }
int somePublicFunction(struct Public * public) {
struct Private private = (struct Private *) public;
// ...
}
int someProtectedFunction(struct Protected * protected) {
struct Private private = (struct Private *) protected;
// ...
}
Then it's just a matter of making sure that dev/include
isn't passed around.
回答3:
For data fields -- just don't use them. You can do some tricks like giving them crazy names to discourage their use, but that won't stop people. The only real way to do it is to make another private struct that is accessed by a void pointer by your library functions.
For private functions -- use file static
functions. Put all of your library functions in one C file and declare the ones that you want to be private as static
and don't put them in any header files.
回答4:
Often by convention, a private member has an extra underscore in its name, or something like _pri
appended. Or possibly a comment. This technique doesn't do compiler-enforced checking to make sure no one inappropriately accesses those fields, but serves as a warning to anyone reading a struct
declaration that the contents are implementation details and they shouldn't peek or poke at them.
Another common technique is to expose your structure as an incomplete type. For example, in your header file, you might have:
struct my_struct;
void some_function(struct my_struct *);
And in the implementation, or some internal header which is not accessible to consumers of the library, you have:
struct my_struct
{
/* Members of that struct */
};
You can also do similar tricks with void pointers, that get cast to the right place in the "private" portion of the code. This approach loses some flexibility (you can't have a stack-allocated instance of an undefined type, for example), but that may be acceptable.
If you want to have a mixture of private and public members, you can do the same thing as above, but store the private struct pointer as a member of the public one, and leave it incomplete in public consumers of the library.
Although, this introduces some indirection, which may hurt performance. There are some (generally non-portable, but will work on reasonable compilers) type-punning tricks you can use too:
struct public_struct
{
int public_member;
int public_member2;
/* etc.. */
};
struct private_struct
{
struct public_struct base_members;
int private_member1;
int private_member2;
};
void some_function(struct public_struct *obj)
{
/* Hack alert! */
struct private_struct *private = (struct private_struct*)obj;
}
This also assumes that you can't store these objects on the stack or in static storage, or get the size at compile time.
回答5:
I feel sorry for you, because mashing different OO concepts into C is often like a square peg in a round hole. That being said, GObject
has support for public and private members, but it's one of my least favourite architectures on earth. If you're not concerned with the minor performance hit, you may be able to do a simpler solution - have a secondary struct that's filled with private members, and have an anonymous pointer to that struct from the primary (public) struct.