就地与分配API

时间:2016-02-17 08:36:39

标签: c api

我需要在C中创建一个库,我想知道如何管理对象:返回已分配的(ex:fopen,opendir)或就地初始化(例如:GNU hcreate_r)。

我知道它主要是a question of taste,我倾向于选择分配API,因为在进行延迟初始化时很方便(通过测试对象指针是否为NULL)。

然而,在阅读Ulrich's paper (PDF)之后,我想知道这种设计是否会引起参考问题的局部性,特别是如果我从其他人那里撰写对象:

struct opaque_composite {
    struct objectx *member1;
    struct objecty *member2;
    struct objectz *member2;
    /* ... */
};

这样一个对象的分配将产生一系列其他子分配。这在实践中是一个问题吗?还有其他我应该注意的问题吗?

2 个答案:

答案 0 :(得分:3)

要考虑的是函数构造的对象类型是否为 opaque 。 opaque类型只在头文件中进行前向声明,你可以用它做的唯一事情就是有一个指向它的指针并将该指针传递给单独编译的API函数。标准库中的FILE是一种不透明的类型。对于opaque类型,您没有选项,但必须提供分配和释放函数,因为用户无法获得对该类型对象的引用。

如果类型不是不透明的 - 也就是说,struct的定义在头文件中 - 拥有一个只执行初始化的函数更通用 - 并且,如果需要,另一个完成 - 但没有分配和解除分配。原因是使用此接口,用户可以决定是否将对象放在堆栈上...

struct widget w;
widget_init(&w, 42, "lorem ipsum");
// use widget…
widget_fini(&w);

......或在堆上。

struct widget * wp = malloc(sizeof(struct widget));
if (wp == NULL)
  exit(1);  // or do whatever
widget_init(wp, 42, "lorem ipsum");
// use widget…
widget_fini(wp);
free(wp);

如果您认为打字过多,您或者您自己的用户可以轻松提供便利功能。

inline struct widget *
new_widget(const int n, const char *const s)
{
  struct widget wp = malloc(sizeof(struct widget));
  if (wp != NULL)
    widget_init(wp, n, s);
  return wp;
}

inline void
del_widget(struct widget * wp)
{
  widget_fini(wp);
  free(wp);
}

反过来是不可能的。

接口应始终提供构成更高级别抽象的基本构建块,但不能通过过度限制来使合法使用成为可能。

当然,这给我们留下了何时制作不透明类型的问题。一个好的经验法则 - 我第一次在coding standards中看到 Linux 内核 - 可能只有在用户无法有意义访问的数据成员时才使类型不透明。我认为应该对此规则进行一些改进,以考虑非透明类型允许在头文件中将“成员”函数作为inline版本提供,从性能的角度来看可能是理想的。另一方面,opaque类型提供更好的封装(特别是因为C无法限制对struct成员的访问)。如果不使用opaque,我也会更容易倾向于透明类型,这会迫使我将#include标题强加到我的库的头文件中,因为它们提供了在我的类型中用作成员的类型的定义。 (对于#include <stdint.h> uint32_t,我可以。#include <unistd.h> #include我当然会尽量避免<curses.h>来自 public HttpEntity<byte[]> getReport(){ WebController.LOGGER.info("Requested report"); try { byte[] reportData= reportService.getReport(); return new HttpEntity<byte[]>(reportData); } catch (ReportGenerationException e) { //WHAT DO I RETURN HERE? } } 等第三方库的标题。

答案 1 :(得分:0)

IMO&#34;级联的子分配&#34;如果保持对象不透明,那么就不会出现问题,因此可以使对象保持一致状态。创建和销毁例程会有一些额外的复杂性,在创建过程中处理分配失败,但没有太繁重。

除了选择使用静态/堆栈分配的副本(我一般都不喜欢),在我看来,一个方案的主要优点如下:

x = initThang(thangPtr);

是返回各种更具体的错误代码的便利性。