Итератор, предоставляющий «представление» своего текущего элемента - PullRequest
0 голосов
/ 10 мая 2019

Задача проектирования, которую я сейчас решаю, состоит в том, чтобы выполнять итерацию по некоторой области памяти и на каждой такой итерации извлекать из этой памяти некоторые метаданные, которые интересуют клиента. В настоящее время я вижу 2 решения:

I.

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.

Так что звонить на next после звонка на current_element_view сабберов current_out.

II.

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_view и current_element_view копирует данные в объект, на который указывает struct queue_element_view *, поэтому next не сгущает данные.

Недостатки:

  1. Включает функцию дополнительного вызова для упрощенияy получить int и char * поля

  2. Это усложняет тестирование публичного API.

Так что я в некотором замешательстве, которыйодин будет предпочтительным / читабельным?Вероятно, есть другая альтернатива?

1 Ответ

3 голосов
/ 10 мая 2019

Альтернатива I

Очевидно, проблема с альтернативой (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() будет означать, что они больше не являются данными для текущего элемента итерации, является функцией , а не ошибкой - почему имеет смысл вмешиваться в клиентов, сохраняющих данные из предыдущие итерации, если они хотят это сделать?

Альтернатива II

Воспринимаемые проблемы вращаются вокруг доступа к членам представления, проходящим через функции. Неясно, как это решает воспринимаемую проблему с помощью альтернативы I. Хотя это может быть частью решения этой проблемы, я не вижу причин считать ее необходимой частью.

Решение : используйте альтернативу I. Серьезно. Если вы собираетесь делать копии данных по мере необходимости, чтобы представление сохранялось надлежащим образом при вызове next(), тогда я не вижу, как вы получаете что-то, делая структуру представления непрозрачной.

В целом

Ваши две альтернативы кажутся странно перевернутыми.

Было бы разумно использовать функции доступа, если вы хотите избежать отдельной структуры представления или копирования данных. Функции будут возвращать данные, относящиеся к текущему элементу итерации - без отдельной структуры представления. После этого у вас будет альтернатива - заставить средства доступа предоставлять копии данных, за которые звонящий несет ответственность, или возложить на собеседника ответственность за копирование любых данных, которые они хотят сохранить, когда итератор выходит вперед.

С другой стороны, если вы предоставляете отдельную структуру для представления элемента, тогда кажется странным, что вы сделали бы это таким образом, чтобы он стал недействительным при продвижении итератора. Отдельный объект представления кажется естественным способом сохранения данных представления до тех пор, пока этого хочет вызывающая сторона.

В любом случае, да, некоторая ответственность возлагается на абонента. Это естественно - бесплатного обеда нет. Четко задокументируйте, каковы эти обязанности, и постарайтесь разработать общий API таким образом, чтобы он соответствовал тому, какие виды ответственности возлагаются на пользователя, при каких обстоятельствах и как эти обязанности должны выполняться.

...