Подтвердить что ты не робот

Предотвращение ненужных копий объектов-объектов С++

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

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));

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

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

4b9b3361

Ответ 1

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

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

Я нашел этот иронический:

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

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

Ответ 2

Если у вас есть недавний компилятор (по крайней мере, 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;
}

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

Ответ 3

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

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

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

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

Ответ 4

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

См. условия для копирования, четко определено в пункте 12.8, абзац 15, пункт 3.

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

[акцент мой]

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

Ответ 5

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

Для С++ 11 вы можете просто пойти с std:: ref и выполнить эту работу.