2015-12-14 3 views
1

Я пишу библиотеку, которая позволяет строить байесовские сети. Структура сети инкапсулируется, и пользователь не может получить доступ к своим полям; однако они могут получить и установить некоторые из них. Предположим, вы хотите написать функцию доступа для поля table (который в основном представляет собой двойной массив), например. Между следующими вариантами, что было бы более уместным?Как определить функцию геттера

Первый вариант:

int getTable(Net *net, double *res) 

Второй вариант:

double *getTable(Net *net) 

В первом варианте, пользователь предоставляет указатель на массив, где будут записаны значения таблицы. Функция копирует значения таблиц на res, оставляя пользователя без прямого доступа к внутренней структуре. Изменение res оставляет таблицу сети неизменной. Конечно, для правильного выделения res предусмотрена другая функция (скажем getTableSize()). Это кажется безопасным (внутренняя структура остается согласованной) и имеет то преимущество, что вы можете вернуть значение кода, если что-то пойдет не так. Недостатком является то, что этот подход может быть медленнее, чем следующий, поскольку он включает в себя копию. Как правило, количество значений table может варьироваться от 1 до нескольких сотен.

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

Какой вариант вы предпочитаете? Что еще нужно рассмотреть? Есть ли другой подход?

+3

Я бы взял прежний подход. Пользователь этой библиотеки может иметь свои предпочтения в отношении распределения динамической памяти (например, не использовать ее вообще). –

+1

ИМО оба подхода имеют свое место на основе требуемой семантики. первый подход заслуживает того, чтобы быть безопасным из-за того, что он не подвергался внутреннему воздействию, но второй подход был бы полезен, например, для функции pop для структурирования данных контейнера – ForeverStudent

ответ

1

Лично я бы выбрал первый вариант из-за возможности вернуть код ошибки. Это также решит вашу проблему относительно того, что пользователь неправильно освободит возвращаемое значение. И прохождение указателя на переменную, объявленную в стеке, легко.

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

const double * getTable(Net *net); 

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

Более подробная информацию о константности можно найти on wikipedia

+0

Хороший вопрос о квалификаторе 'const'. Я принял первый подход по причинам, которые вы упомянули. – nicola

1

Я думаю, что хорошая привычка всегда требовать код возврата от функций, которые по какой-то причине могут выйти из строя.

Обработка ошибок намного эффективнее при работе с кодами возврата.

Я бы выбрал вариант.

Кроме того, я не знаю, если это ошибка или нет, но вариант два возвращает двойной указатель - если это правильное поведение, то функция одна должна иметь подпись:

int getTable(Net *net, double **res) 

Кроме того, как Юджин Ш. упомянутые в комментариях, некоторые среды могут даже не поддерживать malloc (некоторые встроенные микропрограммы встроены в устройства), поэтому предоставление пользователю выбора, передавать ли переменную malloc'd или стек, назначенную переменную, также является хорошей точкой продажи для опции один.

+0

Не ошибся. Поле 'table' является просто массивом' double'. Аргумент 'res' является указателем на массив, который будет заполнен соответствующими значениями. Типичное использование: 'double res [getTableSize (net)]' (или 'double * res = malloc (sizeof (double) * getTableSize (net))'), за которым следует 'getTable (net, res)'. – nicola

1

У меня есть две точек для Вас рассмотреть следующие вопросы:

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

  2. Преждевременная оптимизация - это корень всего зла. Не оптимизируйте свой код, пока не убедитесь (то есть вы измерили), что именно эта часть кода вызывает проблемы.

Это говорит о том, что первый вариант является единственным вариантом.

+0

Согласен с обоими пунктами. Чтобы быть понятным, второй метод - не выделять какую-либо память, поскольку эта память уже существует. Он просто возвращает указатель на него. Предоставлена ​​функция 'deleteNet()', которая освобождает всю память, связанную с сетью. – nicola

0

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

Если вам действительно нужно запретить пользователю изменять данные, используйте mprotect.

Смежные вопросы