Улучшение удобочитаемости без потери производительности?

#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 Я добавил пример того, как это можно масштабировать.