Дополнительные параметры функции: Использовать параметры по умолчанию (ПУСТОЙ УКАЗАТЕЛЬ) или перегрузить функцию?

Я только что написал сценарий в пятницу, чтобы сделать это почти точно. Этот скрипт находит как определенные, так и неопределенные ссылки на символ в данном каталоге:

#!/bin/bash
#findsym.sh
PATTERN=${1:-"symbol"}
OBJDUMP=armdir/arm-linux-gnuabi-objdump

for f in $(find . -name "*.so" -o -name "*.[ao]"); do
    ${OBJDUMP} -tT $f 2>/dev/null | grep -q ${PATTERN}  2>/dev/null
    [[ ${PIPESTATUS[0]} -eq 0 && ${PIPESTATUS[1]} -eq 0 ]] || continue
    echo "FILE: $f"
    ${OBJDUMP} -tT $f | grep  ${PATTERN}
    echo
done

Использование:

 findsym.sh g_queue_pop_nth

Вам придется изменить путь к objdump, и если просматривая сгенерированные C ++ файлы, вы можете добавить -C к вашим флагам objdump.

41
задан Frank 31 March 2009 в 23:15
поделиться

10 ответов

Я определенно одобрил бы 2-й подход перегруженных методов.

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

Со вторым подходом (перегруженные методы) каждый метод имеет ясную цель. Каждый метод хорошо структурирован и связен. Некоторые дополнительные примечания:

  • Если существует код, который должен быть дублирован в оба метода, это может быть извлечено в отдельный метод, и каждый перегруженный метод мог назвать этот внешний метод.
  • Я пошел бы шаг вперед и назвал бы каждый метод по-другому для указания на различия между методами. Это сделает код большим количеством самодокументирования.
28
ответ дан LeopardSkinPillBoxHat 27 November 2019 в 00:16
поделиться

Первый путь более плох, потому что Вы не можете сказать, передали ли Вы случайно в ПУСТОМ УКАЗАТЕЛЕ или если он был сделан нарочно..., если это был несчастный случай затем, Вы, вероятно, вызвали ошибку.

Со вторым можно протестировать (утверждайте, безотносительно) для ПУСТОГО УКАЗАТЕЛЯ и обрабатывают его соответственно.

0
ответ дан TofuBeer 27 November 2019 в 00:16
поделиться

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

// Either at global scope, or (better) inside a class
static vector<int> default_vector = populate_default_vector();

void foo(int i, std::vector<int> const& optional = default_vector) {
    ...
}
1
ответ дан j_random_hacker 27 November 2019 в 00:16
поделиться

Я одобрил бы третью опцию: Распадитесь на две функции, но не перегружайтесь.

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

У меня была бы одна функция, которая берет параметр и тот, который называют "createVectorAndFoo" или чем-то как этот (очевидно, именование становится легче с настоящими проблемами).

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

1
ответ дан Uri 27 November 2019 в 00:16
поделиться

В C++ необходимо постараться не позволять допустимые ПУСТЫЕ параметры, когда это возможно. Причина состоит в том, что это существенно уменьшает callsite документацию. Я знаю, что это звучит как экстремальное значение, но я работаю с API, которые берут вверх 10-20 параметров, половина которых может законно быть ПУСТОЙ. Получающийся код почти нечитабелен

SomeFunction(NULL, pName, NULL, pDestination);

Если необходимо было переключить его для принуждения ссылок константы, код просто вынужден быть более читаемым.

SomeFunction(
  Location::Hidden(),
  pName,
  SomeOtherValue::Empty(),
  pDestination);
2
ответ дан JaredPar 27 November 2019 в 00:16
поделиться

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

2
ответ дан Mehrdad Afshari 27 November 2019 в 00:16
поделиться

Я не использовал бы ни один подход.

В этом контексте цель нечто (), кажется, для обработки вектора. Таким образом, нечто () задание должно обработать вектор.

Но во второй версии нечто (), этому неявно дают вторую работу: создать вектор. Семантика между нечто () версия 1 и нечто () версия 2 не является тем же.

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

Например:

void foo(int i, const std::vector<int>& optional) {
  // process vector
}

std::vector<int>* makeVector() {
   return new std::vector<int>;
}

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

Дизайн, который я имею выше для нечто () функция также, иллюстрирует другой фундаментальный подход, который я лично использую в своем коде когда дело доходит до разработки интерфейсов - который включает функциональные подписи, классы, и т.д. Это - это: Я полагаю, что хороший интерфейс 1) легкий и интуитивный для использования правильно и 2) трудный или невозможный использовать неправильно. В случае нечто () функционируют, мы - implictly, говорящий, что с моим дизайном вектор требуется, чтобы уже существовать и быть 'готов'. Путем разработки нечто () для взятия ссылки вместо указателя это и интуитивно, что у вызывающей стороны должен уже быть вектор, и они собираются быть нелегко передавать в чем-то, что не является готовым к хождению вектором.

42
ответ дан John Dibling 27 November 2019 в 00:16
поделиться

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

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

5
ответ дан 27 November 2019 в 00:16
поделиться

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

  • foo_default (или просто foo)
  • foo_with_values

По крайней мере, я нахожу этот инструмент для очистки различия в длинном терме, и для случайного пользователя библиотеки/функций.

3
ответ дан Diego Sevilla 27 November 2019 в 00:16
поделиться

Я прямо нахожусь в лагере "перегрузки". Другие добавили особенности вашего фактического примера кода, но я хотел добавить то, что, как мне кажется, является преимуществом использования перегрузок по сравнению со значениями по умолчанию для общего случая.

  • Любой параметр может быть установлен по умолчанию.
  • Нет проблем, если функция переопределения использует другое значение по умолчанию.
  • Нет необходимости добавлять «хитрые» конструкторы к существующим типам, чтобы позволить им иметь значение по умолчанию.
  • Выходные параметры могут быть установлены по умолчанию без необходимости использования указателей или хитрых глобальных объектов.

Чтобы добавить несколько примеров кода для каждого:

Любой параметр может быть установлен по умолчанию:

class A {}; class B {}; class C {};

void foo (A const &, B const &, C const &);

inline void foo (A const & a, C const & c)
{
  foo (a, B (), c);    // 'B' defaulted
}

Нет опасности переопределения функций, имеющих разные значения по умолчанию:

class A {
public:
  virtual void foo (int i = 0);
};

class B : public A {
public:
  virtual void foo (int i = 100);
};


void bar (A & a)
{
  a.foo ();           // Always uses '0', no matter of dynamic type of 'a'
}

Нет необходимости добавлять «хакерский» конструкторы к существующим типам, чтобы разрешить их использование по умолчанию:

struct POD {
  int i;
  int j;
};

void foo (POD p);     // Adding default (other than {0, 0})
                      // would require constructor to be added
inline void foo ()
{
  POD p = { 1, 2 };
  foo (p);
}

Выходные параметры могут быть заданы по умолчанию без необходимости использования указателей или хакерских глобальных объектов:

void foo (int i, int & j);  // Default requires global "dummy" 
                            // or 'j' should be pointer.
inline void foo (int i)
{
  int j;
  foo (i, j);
}

Единственное исключение из правила повторной перегрузки по сравнению со значениями по умолчанию - для конструкторов, где это в настоящее время конструктор не может переадресовать другому. (Я считаю, что C ++ 0x решит эту проблему).

2
ответ дан 27 November 2019 в 00:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: