【问题标题】:Partitioning struct into private and public sections?将结构划分为私有和公共部分?
【发布时间】:2010-09-29 17:39:50
【问题描述】:

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

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

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

谢谢。

【问题讨论】:

  • 信息隐藏和封装早于OOD。

标签: c struct private-methods


【解决方案1】:

如您所知,您不能这样做。但是,也有一些成语可以产生类似的效果。

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 函数。

【讨论】:

  • +1 感谢您提醒我有关 Pimple 成语。我从来没有考虑将它应用到 C 语言中。
  • 在 C99 中,您可能需要考虑将指向 struct FooPrivate 的指针替换为灵活的数组成员 struct FooPrivate p[]。这将防止界面用户意外地制作struct Foo 的浅拷贝,因为它会使struct Foo 成为不完整的类型。
  • @caf 您能否详细说明使用灵活数组成员需要对答案进行哪些更改?
  • 分配将是struct Foo *foo = malloc(sizeof(struct Foo) + sizeof(struct FooPrivate));,您不会尝试设置foo->p。其他一切都一样。
  • @caf 这个想法似乎是不可能的,因为我们不能有一个不完整类型的灵活数组成员。此外,不透明指针的整个概念依赖于调用者必须使用指针而不是变量实例,因此调用者无论如何都不能硬拷贝任何东西。 ADT 将不得不提供“复制功能”。
【解决方案2】:

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 不会被传递。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      按照惯例,私有成员的名称中通常有一个额外的下划线,或者附加了类似_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;
      }
      

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

      【讨论】:

      • 使用下划线命名私有变量不是一个好主意,因为它与 C 标准库的标识符命名规则相冲突。
      【解决方案5】:

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

      【讨论】:

      • 我希望远离库包,因为这涉及到许可问题,并且需要尽快完成开发。
      • (a) GObject 是一个开源库,尽管承认它可能会带来许可问题; (b) 二级私有结构不需要库。
      猜你喜欢
      • 2022-12-16
      • 1970-01-01
      • 1970-01-01
      • 2019-01-29
      • 2020-12-04
      • 2013-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多