#c #performance #c 11 #readability
#c #Производительность #c 11 #удобочитаемость
Вопрос:
Давайте предположим, что у меня есть три функции f0, f1, f2
, которые принимают три аргумента. Давайте также предположим, что у меня есть большая функция bigFunc
для вызова, которая принимает возвращаемые значения из первых трех функций в качестве аргументов.
Возможно, я захочу сделать такой вызов:
bigFunc(f0(arg0, arg1, arg2), f1(arg3, arg4, arg5), f2(arg6, arg7, arg8));
Это большой вызов, поэтому я думаю, что было бы гораздо удобнее написать что-то вроде этого:
auto bigArg0 = f0(arg0, arg1, arg2);
auto bigArg1 = f1(arg3, arg4, arg5);
auto bigArg2 = f2(arg6, arg7, arg8);
bigFunc(bigArg0, bigArg1, bigArg2);
Это особенно здорово, если имена bigArg0, bigArg1, bigArg2
позволяют мне быть более конкретным в отношении того, что я делаю (например, если f0, f1, f2
они немного общие; вы можете подумать об алгоритмах STL, которые выполняют разные действия в зависимости от типа итераторов, которые вы ему даете).
Проблема с этим, однако, заключается в том, что, присваивая имена bigArg0, bigArg1, bigArg2
, я делаю их больше не временными, что (я полагаю) сложнее оптимизировать компилятору.
Итак, вот мой вопрос: как правильно это сделать, если я не хочу терять производительность?сделать bigArg0, bigArg1, bigArg2
const ? Дать аргументы bigFunc
сквозному std::move
? Немного того и другого?
Ответ №1:
Имеют ли деструктор и конструктор копирования возвращаемых значений наблюдаемое поведение, как их определяет стандарт?
Если они этого не делают, и у компилятора есть вся необходимая информация, чтобы доказать это, компилятор может удалить копию без необходимости исключения RVO.
Возможно, это так, если вы используете std::move
? В любом случае перемещение может быть лучше, чем копирование…
Или, возможно bigfunc
, получает свои аргументы по постоянной ссылке, и в этом случае оба способа в любом случае приводят к одному и тому же коду…
Ответ №2:
Вот решение, которое позволяет избежать именованных временных:
bigFunc(
f0(arg0, arg1, arg2),
f1(arg3, arg4, arg5),
f2(arg6, arg7, arg8)
);
Увеличенный пример:
bigFunc(
f0(
f0_0(arg0, arg1, arg2),
f0_1(arg3, arg4, arg5),
f0_2(arg6, arg7, arg8)
),
f1(
f1_0(arg9, arg10, arg11),
f1_1(arg12, arg13, arg14),
f1_2(arg15, arg16, arg17)
),
f2(
f2_0(arg18, arg19, arg20),
f2_1(arg21, arg22, arg23),
f2_2(arg24, arg25, arg26)
)
);
Это обрабатывает 27 аргументов и 13 вызовов функций в 17 удобочитаемых строках.
Это уже много чего нужно сделать в одном месте. К тому времени, когда это масштабируется настолько, что теряет читаемость, вы должны начать помещать его части в отдельные функции и / или объединять аргументы в структуры или классы.
Комментарии:
1. Чем это отличается от того, что было у OP и явно не хочет ?
2. Да, хорошо, отступ «работает» в этом случае, но предположим, что arg0, arg1, arg2 получены с помощью других вызовов функций… ваше «решение» не масштабируется
3. @Deduplicator, предоставляя точный формат, который запрашивал OP для удобства чтения, вместо трудночитаемой единственной строки.
4. @Aberrant: это слишком буквальный пример, тем более что OP похвально использовал мало кода для демонстрации своего вопроса…
5. @Eternal Я добавил пример того, как это можно масштабировать.