Есть ли какое-то преимущество в использовании std :: option, чтобы избежать аргументов по умолчанию в функции?

Я портирую код на C ++ 17, пытаясь использовать новые возможности, пока это возможно. Одна вещь, которая мне нравится, это идея использовать std::optional возвращать или нет значение в функции, которая может не работать в некоторых условиях.

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

void compute_something(int a, int b, const Object& c = Object(whatever)) {
// ...
}

становится:

void compute_something(int a, int b, std::optional<Object> c) {
auto tmp = c.value_or(Object(whatever));
// ...
}

Согласно официальному документу:

Если необязательный параметр содержит значение, оно гарантированно будет
выделяется как часть необязательного следа объекта, то есть без динамического
распределение памяти когда-либо происходит. Таким образом, необязательный объект моделирует
объект, а не указатель, хотя оператор * () и оператор -> ()
определены.

Таким образом, каждый раз, когда мы используем std :: необязательный для передачи аргументов, это подразумевает создание копий, что может быть наказанием, если объект большой.

Мне нравится эта идея, потому что она делает код проще и понятнее, но есть ли какое-то преимущество?

2

Решение

Трудно дать хороший общий ответ, не зная, что конкретно делает ваша функция, но да, у использования явных преимуществ optional, В произвольном порядке:


Во-первых, как ты размножать аргументы по умолчанию при переносе функций? Со стандартными аргументами языка по умолчанию, вы просто должны знать, что все значения по умолчанию:

int foo(int i = 4);
int bar(int i = /* foo's default that I have to know here */) { return foo(i); }

И теперь, если я изменюсь fooпо умолчанию 5Я должен знать, чтобы изменить bar — что, как правило, они просто не синхронизированы. С optionalтолько реализация foo нужно знать значение по умолчанию:

int foo(optional<int> );
int bar(optional<int> o) { return foo(o); }

Так что это не проблема.


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

// you just have to know that -1 means no fd
int foo(int fd = -1);

Но с optional, это четко выражено в подписи и типе — вам не нужно знать, что такое страж:

int foo(std::optional<int> fd);

Отсутствие сторожа может оказать положительное влияние на производительность и для объектов большего размера, так как вместо того, чтобы создавать его, чтобы иметь это значение, вы просто используете nullopt,


В-третьих, если optional когда-либо начинает поддерживать ссылки (и многие сторонние библиотеки делают), optional<T const&> это фантастический выбор для неопределяемого по умолчанию аргумента. Там действительно нет эквивалента аргументам по умолчанию.

3

Другие решения

std::optional не является заменой для параметра функции по умолчанию:

void compute_something(int a, int b, const Object& c = Object(whatever))

Это может быть вызвано compute_something(0, 0);

void compute_something(int a, int b, std::optional<Object> c)

Это не может быть скомпилировано. compute_something(0, 0); не скомпилируется. По крайней мере, вы должны сделать compute_something(0, 0, std::nullopt);,

Таким образом, каждый раз, когда мы используем std :: необязательный для передачи аргументов, это подразумевает
создание копий, чем может быть наказание производительности, если объект
большой.

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

Но вы можете сделать несколько трюков, сочетая std::optional с станд :: reference_wrapper:

#include <optional>
#include <utility>
#include <functional>
#include <iostream>

class X {

public:
X()
{
std::cout << "Constructor" << std::endl;
}

~X()
{
std::cout << "Destructor" << std::endl;
}

void foo() const
{
std::cout << "Foo" << std::endl;
}

X(const X &x)
{
std::cout << "Copy constructor" << std::endl;
}

X &operator=(const X &)
{
std::cout << "operator=" << std::endl;
}
};

void bar(std::optional<std::reference_wrapper<const X>> arg)
{
if (arg)
arg->get().foo();
}

int main()
{
X x;

bar(std::nullopt);

bar(x);
return 0;
}

С gcc 7.2.1 единственный выход из этого:

Constructor
Foo
Destructor

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

if (arg)
{
const X &x=arg->get();

// Going forward, just use x, such as:

x.foo();
}

Теперь давайте сделаем еще один шаг:

void bar(std::optional<std::reference_wrapper<const X>> arg=std::nullopt)

При этом два вызова функции могут быть просто:

bar();
bar(x);

Ты можешь взять свой пирог и съесть его тоже. Вам не нужно явно указывать std::nulloptлюбезно предоставлено значение параметра по умолчанию; вам не нужно создавать весь объект по умолчанию, и при явной передаче объекта он все равно передается по ссылке. У вас просто накладные расходы std::optional сам по себе, который в большинстве реализаций C ++ занимает всего несколько дополнительных байтов.

1

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

// user may or may not supply an item value
// if item is not supplied then the stock item will be constructed
// user can not choose to supply an empty item
void foo(t_Item item = t_Item{42});

// user must supply an optional item value
// though he can choose to supply an empty item
void foo(optional<t_Item> item);

// user may or may not supply an optional item value
// but he can choose to supply an empty item as well
// if no optional item value is supplied then the stock item will be constructed
void foo(optional<t_Item> item = optional<t_Item>{t_Item{42}});
0
По вопросам рекламы [email protected]