【问题标题】:Iterator providing a "view" of its current element迭代器提供其当前元素的“视图”
【发布时间】:2019-05-10 15:45:19
【问题描述】:

我目前正在解决的设计问题是迭代某个内存区域,并在每次这样的迭代中从该内存中检索客户感兴趣的一些元数据。我目前看到 2 个解决方案:

我。

struct queue;

struct queue_element_view{
    int id;
    char *description;
};

//0 - if ok, -1 - if end of queue reached
int next(struct queue*);

//0 - if ok, -1 - if end of queue reached    
int current_element_view(struct queue*, struct queue_element_view *);

因此,队列不透明结构可以通过next 函数进行遍历,并且由于队列元素是平台相关的,并且我想保持库跨平台,因此我提供了一个独立于平台的struct queue_element_view,它在所有平台上都是合理的。

缺点:

如果客户端这样写代码:

struct queue *queue_ptr = //
struct queue_element_view current_out;
current_element_view(queue_ptr, &current_out);
//current_out now contains current's element meta data
next(queue_ptr);
//current_out now may contain unspecified data
//since queue's current element changed.

所以在调用current_element_view 之后再调用next 会破坏current_out

二。

struct queue;

struct queue_element_view;

struct queue_element_view *allocate_view(void); 

int * get_id(struct queue_element_view *);

char * get_description(struct queue_element_view *);

//0 - if ok, -1 - if end of queue reached
int next(struct queue*);

//0 - if ok, -1 - if end of queue reached    
int current_element_view(struct queue*, struct queue_element_view *);

在这种情况下,我们分配了struct queue_element_viewcurrent_element_view 将数据复制到结构queue_element_view * 指向的对象,因此next 不会破坏数据。

缺点:

  1. 它涉及一个函数附加调用来简单地检索intchar *字段

  2. 这使得测试公共 api 变得更加复杂。

所以我有点困惑哪一个更可取/可读?或许还有其他选择?

【问题讨论】:

  • 你能解释一下这个问题吗?数据会损坏吗?还是只是过时了?
  • @AvivGoll 大部分已经过时,但如果调用next 后的实现回收(尚未)前一个元素的内存,它将包含悬空指针。我倾向于在从客户端遍历的过程中隐藏内存回收细节
  • @St.Antario,如果您的想法是next() 将回收先前由current_element_view() 分配的内存,那么这确实应该成为问题的一部分。我认为这比目前在该替代方案的描述中提出的任何问题都更具潜在问题。
  • @JohnBollinger 以下是合理的选择吗?我让struct queue_element_view 不透明并添加函数const struct queue_element_view * current_element_view_pointer(struct queue *);。它返回的指针始终包含当前元素的相关数据,客户端不可修改,但在调用next时保持一致?
  • id不能悬空。您可以复制描述并为视图添加免费方法。关于过时的问题,第二个解决方案是如何解决的?

标签: c api-design software-design opaque-pointers


【解决方案1】:

备选方案一

对于备选方案 (I) 的感知问题显然是调用 next() 将导致先前复制到 struct queue_element_view 的数据变为无效,因为 next()(可能)释放内存。

解决方案:确保next() 不会这样做。这可能意味着您必须制作描述字符串的副本以放入视图中,而不是仅仅为客户端提供原始指针本身的副本。在这种情况下,提供一个函数来释放 struct queue_element_view 中反映的任何内部分配可能会有所帮助,可能是这样的:

void queue_element_view_clean(struct queue_element_view *view) {
    free(view->description);
}

这使客户不必了解需要清理的内容、如何清理以及不需要清理的细节。然后,他们可以随心所欲地保留数据,并在他们决定使用完数据后对其进行清理。对next() 的调用将意味着它们不再是迭代当前元素的数据是一个特性,而不是一个错误——为什么干扰客户保留数据是有意义的如果他们想这样做,可以从以前的迭代中提取?

备选方案二

感知到的问题围绕着通过函数访问视图成员。目前尚不清楚这如何解决备选方案 I 的感知问题。虽然它可能是该问题解决方案的一部分,但我认为没有理由认为它是必要部分。

解决方案:使用替代方案 I。认真。如果您要根据需要制作数据副本,以便在调用next() 时适当地保留视图,那么我看不出您如何通过使视图结构不透明来获得任何收益。

总体

你的两个选择似乎奇怪地颠倒了。

如果您想避免使用单独的视图结构或复制数据,那么使用访问器函数对我来说是有意义的。这些函数将返回与迭代的当前元素有关的数据——不涉及单独的视图结构。然后,您可以选择让访问者提供调用者负责的数据的副本,或者让调用者负责在迭代器前进时复制他们想要保留的任何数据。

另一方面,如果您为元素视图提供单独的结构,那么您会以一种允许它在迭代器高级时变为无效的方式这样做似乎很奇怪。单独的视图对象似乎是一种很自然的方式,可以让视图数据在调用者需要时保留。

无论如何,是的,调用者需要承担某种责任。这是很自然的——没有免费的午餐。清楚地记录这些责任是什么,并尝试以一种感觉一致的方式设计整个 API,即用户承担了哪些责任、在什么情况下以及如何履行这些责任。

【讨论】:

  • 复制似乎是第一种选择的好解决方案。泰。
猜你喜欢
  • 1970-01-01
  • 2019-11-22
  • 2010-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-15
相关资源
最近更新 更多