Предотвратить ненужные копии объектов функтора C ++ - PullRequest
16 голосов
/ 07 февраля 2010

У меня есть класс, который накапливает информацию о наборе объектов и может действовать как функтор или итератор вывода. Это позволяет мне делать такие вещи, как:

std::vector<Foo> v;
Foo const x = std::for_each(v.begin(), v.end(), Joiner<Foo>());

и

Foo const x = std::copy(v.begin(), v.end(), Joiner<Foo>());

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

Если я создаю функтор как lvalue, компилятор создает две дополнительные копии вместо одной:

Joiner<Foo> joiner;
Foo const x = std::copy(v.begin(), v.end(), joiner);

Если я неуклюже заставляю тип шаблона ссылаться на него, который он передает в ссылке, но затем все равно делает его копию и возвращает висячую ссылку на (теперь уничтоженную) временную копию:

x = std::copy<Container::const_iterator, Joiner<Foo>&>(...));

Я могу сделать копии дешевле, используя ссылку на состояние, а не само состояние в функторе в стиле std::inserter, что приводит к чему-то вроде этого:

Foo output;
std::copy(v.begin(), v.end(), Joiner<Foo>(output));

Но это делает невозможным использование «функционального» стиля неизменяемых объектов, и, как правило, это не так приятно.

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

Ответы [ 5 ]

15 голосов
/ 07 февраля 2010

Вы столкнулись с часто жаловавшимся поведением с <algorithm>. Нет никаких ограничений на то, что они могут делать с функтором, поэтому ответ на ваш вопрос - нет: нет способа побудить компилятор исключить копии. Это не (всегда) компилятор, это реализация библиотеки . Они просто любят передавать функторы по значению (представьте, что std :: sort выполняет qsort, передает функтор по значению рекурсивным вызовам и т. Д.).

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

Я нашел эту иронию:

Но это делает невозможным использование «функционального» стиля неизменяемых объектов, и, как правило, это не так приятно.

... поскольку весь этот вопрос основан на том, что у вас сложный функтор с состоянием, где создание копий проблематично. Если бы вы использовали неизменяемые объекты в «функциональном» стиле, это не было бы проблемой - дополнительные копии не были бы проблемой, не так ли?

4 голосов
/ 07 февраля 2010

Если у вас есть недавний компилятор (по крайней мере, Visual Studio 2008 SP1 или GCC 4.4, я думаю), вы можете использовать std :: ref / std :: cref

#include <string>
#include <vector>
#include <functional> // for std::cref
#include <algorithm>
#include <iostream>

template <typename T>
class SuperHeavyFunctor 
{
    std::vector<char> v500mo;
    //ban copy
    SuperHeavyFunctor(const SuperHeavyFunctor&);
    SuperHeavyFunctor& operator=(const SuperHeavyFunctor&);
public:
    SuperHeavyFunctor():v500mo(500*1024*1024){}
    void operator()(const T& t) const { std::cout << t << std::endl; }
};

int main()
{
    std::vector<std::string> v; v.push_back("Hello"); v.push_back("world");
    std::for_each(v.begin(), v.end(), std::cref(SuperHeavyFunctor<std::string>()));
    return 0;
}

Редактировать: На самом деле реализация reference_wrapper в MSVC10, похоже, не знает, как определить тип возвращаемого значения объекта функции operator (). Мне пришлось извлечь SuperHeavyFunctor из std::unary_function<T, void>, чтобы он работал.

2 голосов
/ 27 февраля 2010

Просто быстрая заметка, for_each , накапливать , преобразование (2-я форма) , не дают гарантии заказа при обходе диапазон.

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

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

Будьте осторожны при создании функторов с сохранением состояния.

1 голос
/ 01 июня 2014

Для решения, которое будет работать с кодом pre-c ++ 11, вы можете рассмотреть возможность использования boost :: function вместе с boost :: ref (так как * boost :: reference_wrapper не имеет перегруженного оператора ( ) , в отличие от std :: reference_wrapper, который действительно делает). На этой странице http://www.boost.org/doc/libs/1_55_0/doc/html/function/tutorial.html#idp95780904, вы можете дважды обернуть ваш функтор внутри объекта boost :: ref, а затем объекта boost :: function. Я попробовал это решение, и оно сработало безупречно.

Для c ++ 11 вы можете просто использовать std :: ref, и он сделает свою работу.

1 голос
/ 07 февраля 2010

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

См. Условия для исключения копирования четко определены в 12.8, пункт 15, пункт 3.

когда временный объект класса, который имеет не привязан к ссылке (12.2) будет скопирован в объект класса с тот же CV-неквалифицированный тип , копия операция может быть опущена строительство временного объекта прямо в цель опущенная копия

[Акцент мой]

LHS Foo квалифицирован const, временный - нет. ИМХО, это исключает возможность копирования-исключения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...