Допустим, я реализую функцию func
.
func
будет вызываться с двумя параметрами.func
будет вызываться только с одним параметром, но внутри функции будет использоваться значение по умолчанию для отсутствующего второго параметра.Я могу придумать 3 возможных способа реализации этой функциональности:
void func(int a, long b = c);
func
будет вызываться следующим образом:
func(a);
func(a, b);
void func(int a);
void func(int a, long b);
func
будет вызываться следующим образом:
func(a);
func(a, b);
void func(int a, optional<long> b);
func
будет вызываться следующим образом:
func(a, optional<long> ());
func(a, b);
Я хочу знать, как лучше всего реализовать желаемую функциональность. Любые предложения приветствуются.
Вы спрашиваете о читабельности? Если это так, я думаю, что аргумент по умолчанию великолепен, потому что он показывает выбор используемого значения в определении.
Есть два вопроса, которые нужно задать себе при рассмотрении этого вопроса:
Если второй аргумент имеет логическое значение по умолчанию, а функция делает одно и то же, несмотря ни на что, то аргумент по умолчанию работает хорошо. Например:
std::vector<std::string> split_string(const std::string& str, char sep = ' ');
По умолчанию это разбивает строку на пробелы, но для изменения этого можно указать разделитель. Нет особого смысла разбивать это на две перегрузки или использовать здесь std::optional
.
Если второй аргумент не имеет логического значения по умолчанию, но функция остается в основном такой же, если он не задан, то std::optional
имеет больше смысла. Например:
void extract(const std::filesystem::path& archive_file,
const std::filesystem::path& output_dir,
std::optional<std::regex> exclude_filter = {});
Здесь мы извлекаем файлы из архивного файла и записываем извлеченные файлы на диск, при желании исключая файлы, соответствующие некоторому шаблону. Определение функции будет в основном одинаковым с фильтром или без него; это всего лишь одна дополнительная разница в строке:
if (exclude_filter && std::regex_match(file, *exclude_filter) continue;
Дублировать определение не имеет особого смысла, поэтому перегрузка не имеет смысла. В то же время не существует регулярного выражения «ничего не соответствует», поэтому нет логического фильтра по умолчанию, который можно было бы применить. std::optional
идеально подходит здесь.
Обратите внимание, что я по-прежнему использовал аргумент по умолчанию. Если по умолчанию ваш std::optional
пуст, это может сделать ваши звонки намного приятнее.
Наконец, если реализация функции принципиально отличается в версиях с одним и двумя аргументами, используйте перегрузку. Например:
void hide(window& win) {
win.set_visible(false);
}
template <typename rep, typename period>
void hide(window& win, const std::chrono::duration<rep, period>& fade_time) {
auto wait_time = equal_intervals(fade_time, win.opacity());
while (win.oapcity() > 0) {
win.set_opacity(win.opacity() - 1);
std::this_thread::sleep_for(wait_time);
}
}
Здесь мы скрываем окно с необязательным временем затухания. Хотя логически функции делают одно и то же, они реализованы совершенно по-разному. Быть одной и той же функцией не имеет большого смысла. Несмотря на то, что 0
было бы логическим временем затухания по умолчанию, по-прежнему не имеет смысла использовать здесь аргумент по умолчанию, поскольку вы просто получите большой блок if
:
if (fade_time == 0) {
// body of the first overload
} else {
// body of the second overload
}
Спасибо за подробное объяснение, Майлз :)
Вариант 3 лучше, чем кажется на вопросе. И
{}
, иnullopt
работают.