17

在 C++ 和 Java 中,数据结构可以有private,publicprotected区域。我想将这个概念移植到我正在编写的 C 语言程序中。

是否有任何用于在 C 中实现私有或受保护的函数指针和数据字段的习语struct我知道 Cstruct是公共的,我正在寻找一个习惯用法来帮助隐藏一些实现细节并强制用户使用公共接口。

注意:商店已经选择了语言,所以我被困在 C 中实现面向对象的概念。

谢谢。

4

5 回答 5

30

如您所知,您不能这样做。但是,有些习语会产生类似的效果。

C 将允许您做一些类似于面向对象设计中所谓的“pimpl”习语的事情。您的结构可以有一个不透明的指针,指向另一个前向声明的结构,该结构充当结构的私有数据。对结构进行操作的函数,代替成员函数,可以拥有私有成员的完整定义,并且可以使用它,而代码的其他部分则不能。例如:

在标题中,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" */

在实现中,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 */
  }

在一个程序中:

  #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;
  }

注意需要使用“构造函数”来为私有数据分配内存。显然,您需要将其与特殊的“析构函数”功能配对,以便正确释放私有数据。

或者,或者,如果您希望您的结构没有任何公共字段,您可以使整个结构不透明,并且只让标题类似于

  struct Foo;

  extern struct Foo* Foo_Create(); /* "constructor" */

  extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */

使用 foo.c 中的实际定义struct Foo,以及可用于您希望提供直接访问的任何属性的 getter 和 setter 函数。

于 2010-09-29T17:50:13.630 回答
10

C中有时使用的概念是

// 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;
  // ...
}

这使用了标准技巧,即指向结构的指针可以与指向结构中第一个元素的指针互换。

如果您希望所有字段都是私有的,那就更容易了:

// lib2.h
typedef struct AllPrivate * Handle;
Handle getHandle();
int function2(Handle handle);

// lib2.c
struct AllPrivate { /* ... */ }

#includelib2.h 不会抱怨的文件,因为我们只使用,struct AllPrivate *并且所有指针大小相同,所以编译器不需要知道struct AllPrivate.

要做一个受保护的区域,你只需要定义

// 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;
  // ...
}

那么这只是一个确保它dev/include不会被传递的问题。

于 2010-09-29T17:46:46.277 回答
2

对于数据字段——只是不要使用它们。你可以做一些技巧,比如给它们起个疯狂的名字来阻止它们的使用,但这不会阻止人们。唯一真正的方法是创建另一个私有结构,由您的库函数通过 void 指针访问。

对于私有函数——使用文件static函数。将所有库函数放在一个 C 文件中,并声明要私有的函数,static不要将它们放在任何头文件中。

于 2010-09-29T17:46:46.913 回答
1

通常按照惯例,私有成员的名称中有一个额外的下划线,或者类似_pri附加的东西。或者可能是评论。这种技术不会进行编译器强制检查以确保没有人不恰当地访问这些字段,而是向任何阅读struct声明内容是实现细节的人发出警告,他们不应该偷看或戳它们。

另一种常见的技术是将您的结构公开为不完整的类型。例如,在您的头文件中,您可能有:

struct my_struct;

void some_function(struct my_struct *);

在实现中,或者库的消费者无法访问的一些内部标头中,您有:

struct my_struct
{
    /* Members of that struct */
};

您还可以使用 void 指针执行类似的技巧,将其强制转换到代码的“私有”部分中的正确位置。这种方法失去了一些灵活性(例如,您不能拥有未定义类型的堆栈分配实例),但这可能是可以接受的。

如果您想要混合使用私有和公共成员,您可以执行与上述相同的操作,但将私有结构指针存储为公共成员,并使其在库的公共消费者中不完整。

虽然,这会引入一些间接性,这可能会损害性能。您也可以使用一些(通常不可移植,但适用于合理的编译器)类型双关技巧:

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;
}

这还假设您不能将这些对象存储在堆栈或静态存储中,或者在编译时获取大小。

于 2010-09-29T18:03:21.463 回答
0

我为你感到难过,因为将不同的 OO 概念混搭到 C 中通常就像是圆孔中的方钉。话虽如此,GObject它支持公共和私人成员,但它是地球上我最不喜欢的架构之一。如果您不关心较小的性能损失,您可以做一个更简单的解决方案 - 拥有一个填充私有成员的辅助结构,并拥有一个从主要(公共)结构指向该结构的匿名指针。

于 2010-09-29T17:43:35.953 回答