Будет ли C ++ lambda действительно копировать параметры, захваченные копией?

Использование лямбда-функции в C ++ с переменными, захваченными по значению, подразумевает копия значения.

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

Например, в дальнейшем кажется имеет смысл передать new_item в качестве значения, поскольку оно используется в режиме только для чтения.

void loadavg_file::add(loadavg_item const & new_item)
{
    auto const & it(std::find_if(
            f_items.begin(),
            f_items.end(),
            [new_item](auto const & item)
            {
                return (item.f_address == new_item.f_address);
            }));

    if(it == f_items.end())
    {
        f_items.push_back(it);
    }
    else
    {
        // replace existing item with new avg and timestamp
        it->f_timestamp = new_item.f_timestamp;
        it->f_avg = new_item.f_avg;
    }
}

Будет ли цикл оптимизирован и в результате не будет абсолютно никакой копии new_item?


person Alexis Wilke    schedule 09.08.2016    source источник
comment
Нет никаких гарантий. Хороший компилятор может оптимизировать копию, но это не обязательно.   -  person Jesper Juhl    schedule 09.08.2016
comment
Единственное, что вы знаете, это то, что код должен вести себя так же, как и раньше. Если нет разницы между встраиванием всего этого и отсутствием копий / объектов и тем, что вы написали, компилятор может это сделать. Будет ли он? скомпилируйте его и проверьте сборку.   -  person NathanOliver    schedule 09.08.2016
comment
Мне этот вопрос кажется более интересным, чем двум другим комментаторам. AFAIK, компилятор может исключать копии только при определенных условиях, и я не знаю, является ли лямбда-захват одним из них. (Это не имеет значения, если копии тривиальны, но OP, вероятно, не позаботится, чтобы копии были тривиальными.)   -  person zneak    schedule 09.08.2016
comment
@zneak Речь идет не о копировании, а о замене кода ИМХО. Все это может быть встроено в цикл с телом. AFAIK правило «как если бы» позволяло это.   -  person NathanOliver    schedule 09.08.2016
comment
@NathanOliver, замена кода все равно должна будет сделать копию, если исключение копирования не разрешено (и если копии не являются тривиальными).   -  person zneak    schedule 09.08.2016
comment
gcc создает копию, посмотрите на сборку (clang тоже)   -  person m.s.    schedule 09.08.2016
comment
@РС. Это также может зависеть от типов элементов данных loadavg_item. Что, если они все тривиальные типы? Невозможно точно сказать, всегда ли компилятор создает копию или нет. В любом случае мне непонятно, почему OP вообще не захватывает new_item по ссылке.   -  person Praetorian    schedule 09.08.2016
comment
@Praetorian, вы, конечно, правы, но поскольку OP не предоставил минимальный воспроизводимый пример, мне пришлось угадать   -  person m.s.    schedule 09.08.2016
comment
@Praetorian, мы не можем сказать, всегда ли компилятор будет следовать какой-либо оптимизации, но мы можем определить, по крайней мере разрешена эта оптимизация или нет. Я абсолютно готов поддаться влиянию, но сейчас мне не ясно, что это такое.   -  person zneak    schedule 09.08.2016
comment
@zneak Не уверен. Это выглядит как совершенно верный способ оптимизировать то, что есть в OP, и копия не была сделана или даже нужно было исключить.   -  person NathanOliver    schedule 09.08.2016
comment
@NathanOliver, я не уверен, что это действительный способ оптимизации данного кода именно потому, что вы исключаете копию, которую, по-видимому, подразумевает захват по значению.   -  person zneak    schedule 09.08.2016
comment
Это может сделать этот вопрос лучше, если его спросят, как будто компилятору разрешено брать то, что есть у OP, и создавать этот оптимизированный код. Прямо сейчас вопрос в том, произойдет ли это, и на него сложнее ответить, поскольку его нужно будет протестировать со всеми компиляторами и настройками.   -  person NathanOliver    schedule 09.08.2016
comment
@NathanOliver, мы не можем дать авторитетное «да» для любого компилятора, если оптимизация возможна, но мы можем дать авторитетное «нет», если это не так, если кто-то действительно испытывает трудности с проверкой. Спрашивая, можно ли избежать копирования с помощью хорошего компилятора, OP по существу спрашивает, разрешено ли это, а не делает ли это какая-либо конкретная реализация в настоящее время.   -  person zneak    schedule 09.08.2016
comment
@zneak Ну, мы можем обходиться этим весь день, но я не понимаю, почему в соответствии с правилом «как если бы» должна быть сделана копия, или чтобы функтор был создан из лямбды, или для Цикл существует из вызова find_if, если компилятор может определить, что развертывание цикла имеет больше смысла в этом случае и что в loadavg_item есть только один член данных, с которым вы заботитесь о сравнении, и указанный член данных имеет некоторый тривиальный тип.   -  person Praetorian    schedule 09.08.2016
comment
@Praetorian, потому что семантика лямбда-выражений заключается в создании объекта, который имеет одно поле для каждой захваченной переменной, что подразумевает, что поля создаются копией из оригиналов. Правило as-if позволяет компилятору встраивать функции и разворачивать циклы, но не позволяет ему отбрасывать побочные эффекты копии. Это покрывается правилами исключения копирования.   -  person zneak    schedule 09.08.2016


Ответы (2)


Если конструктор копирования new_item (то есть loadavg_item::loadavg_item(loadavg_item const&)) имеет наблюдаемые эффекты, отличные от выделения памяти, тогда эти эффекты должны наблюдаться (если, знаете, вы действительно пытаетесь их наблюдать).

Это потому, что вы можете зависеть от этих побочных эффектов, и это не тот контекст, в котором разрешено исключение копирования; исключение копирования разрешено (и, в последнее время, обязательно), когда возвращает значение из функции. С другой стороны, исключение выделения памяти разрешено где угодно (в соответствии с правилами в [expr.new] / 10); clang особенно хорош в этом.

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

Если конструктор копирования new_item не является встроенным, тогда сборка единицы перевода может отображать вызов конструктора копирования в виде символа, но оптимизация времени компоновки (LTO) все же может исключить этот вызов, если оптимизатор времени компоновки может сделать вывод, что конструктор копирования не имеет наблюдаемых побочных эффектов.

person ecatmur    schedule 09.08.2016
comment
Означает ли это, что использование ссылки с большей вероятностью увеличит вероятность получения оптимизации? Другими словами, как программисты на C ++, должны ли мы использовать ссылку почти во всех случаях в целях оптимизации (если, конечно, лямбда-функция действительно не должна вносить изменения в копию объекта)? - person Alexis Wilke; 09.08.2016
comment
@Alexis: Я бы не стал так говорить. Ссылка гарантированно избегает копирования, а также может уничтожить другие оптимизации, потому что компилятор должен обрабатывать случаи с псевдонимом. - person Ben Voigt; 09.08.2016

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

Если все дерево вызовов (find_if, конструктор копирования, деструктор, operator==, любые вызываемые ими функции) состоит из видимых функций, и компилятор решил встроить их, возможно, что дальнейшие оптимизации, такие как исключение общих подвыражений, могут уменьшить или исключить стоимость выполнения этих копий.

В процессе компилятор должен будет доказать, что

  • Значение копии не меняется (потому что такие изменения не должны распространяться на оригинал)
  • Конструктор копирования не имеет побочных эффектов.
  • Деструктор не имеет побочных эффектов.
  • Ни одна из функций не зависит от идентичности объекта.
  • Время жизни копии не может превышать время жизни объекта, на который ссылается параметр.
  • Для объекта, на который указывает параметр, не существует других псевдонимов, или такие другие псевдонимы не будут использоваться для изменения указанного объекта, пока используется лямбда, включая доступ из других потоков (если синхронизирован).

Если вы хотите избежать копирования, не пишите код, запрашивающий эту копию. Или скопируйте только необходимую часть объекта, например

auto const & it(std::find_if(
        f_items.begin(),
        f_items.end(),
        [key = new_item.f_address](auto const & item)
        {
            return (item.f_address == key);
        }));
person Ben Voigt    schedule 09.08.2016