Доброго времени суток!
В процессе размышлений над новыми возможностями привнесенными в стандарт языка С++, в частности rvalue-ссылками у меня возник вот какой вопрос.
Насколько я понял, rvalue-ссылки позволяют оптимизировать возвращаемое значение, т.е. вместо копирования памяти происходит перемещение объекта в памяти (из одной области в другую). До нового стандарта, насколько мне известно, данная проблема решалась через expression templates, означает ли что применяя rvalue-ссылки можно забыть об expression templates? Или я что-то неверно понимаю?
UPDATE: Уточню вопрос. Предположим у нас есть класс вида:
namespace math {
template
class vector
{
public:
vector() : mptr(0), msize(0) {
}
vector(size_t n) : msize(n) {
mptr = new T[n];
}
vector(const vector& other) : msize(other.msize) {
if (mptr) delete[] m_ptr;
if (msize == 0) {
mptr = 0;
} else {
mptr = new T[msize];
std::copy(other.mptr, other.mptr + msize, m_ptr);
}
}
~vector() {
if (mptr)
delete[] mptr;
}
vector& operator=(const vector& other) {
msize = other.msize;
if (mptr) delete[] m_ptr;
if (msize == 0) {
mptr = 0;
} else {
mptr = new T[msize];
std::copy(other.mptr, other.mptr + msize, m_ptr);
}
}
vector& operator+=(const vector& x) {
assert(x.size() == msize)
std::transform(x.m_ptr, x.mptr+msize, mptr, std::plus());
}
vector& operator-=(const vector& x) {
assert(x.size() == msize)
std::transform(x.m_ptr, x.mptr+msize, mptr, std::plus());
}
// ... и так далее
private:
T* mptr;
size_t msize;
};
} // end namespace math
Теперь предположим мы хотим перегрузить бинарные операторы (+,-, и т.д.) :
template
vector operator+ (const vector& x, const vector& y)
{
vector res(x); // временная переменная - копия x
res += y; // складываем x+y
return res; // возвращаем результат
}
Теперь мы воспользуемся нашим классом:
math::vector a(1 << 18);
math::vector b(1 << 18);
math::vector c(1 << 18);
math::vector d(1 << 18);
math::vector r = a * b - c + d;
При выполнении этого кода будет создано как минимум 3 временных массива размера 2^18, в каждом операторе, которых можно избежать. Собственно это будет приводить к снижению производительности. Раньше это обходилось с помощью expression templates, которые разворачивали выражение a * b - c + d в один цикл следующим образом:
math::vector r(n);
for (size_t i = 0; i < n; i++)
r[i] = a[i]* b[i] - c[i] + d[i];
В новом С++11 мы добавим к нашему классу конструктор перемещения и оператор перемещения (если я их правильно назвал), и объявление нашего класса поменяется следующим образом:
namespace math {
template
class vector
{
public:
vector();
vector(size_t n);
vector(const vector&);
// Перемещающий кострктор
vector(const vector&& other);
vector& operator=(const vector&);
// Перемещающий оператор присваивания
vector& operator=(const vector&&);
// ...
};
}
Значит ли это, что теперь мы избавлены от копий и можем забыть про expression templates?
Не об этом ли речь в http://www.artima.com/cppsource/rvalue.html в разделе 'Eliminating spurious copies'?
P.S. Код писал без проверки - только чтобы показать идеи, так что за ошибки не пинайте :)
Заранее спасибо за ответы!
Ответ
Хм. Насколько я понимаю, смысл rvalue-ссылок в том, что перемещения данных не происходит. То есть, данные, которыми пользовался объект, можно «присвоить» себе без копирования, т. к. объект всё равно собирается исчезнуть.
Статья по теме rvalue references: http://www.artima.com/cppsource/rvalue.html (Бьярн среди авторов).
Обновление:
Я не встречался с expression template'ами раньше. Если они и правда делают то, о чём вы говорите, то rvalue references слабее, и не являются адекватной заменой.
Смотрите. Если у вас есть код
math::vector r = a * b - c * d + e;
(я специально сделал умножение из сложения) и нужные move-конструкторы, что произойдёт? Этот код будет проинтерпретирован как
math::vector&& tmp1 = a * b;
math::vector&& tmp2 = c * d;
math::vector&& tmp3 = tmp1 - tmp2;
math::vector r = tmp3 + e;
Будет вызван дважды operator * (const math::vector& l, const math::vector& r), который обязан выделять новую память, т. к. не может отобрать её у операндов. Затем будет вызван operator - (const math::vector& l, const math::vector& r), который так же не сможет отобрать память у операндов. Далее, будет вызван operator + (const math::vector& l, const math::vector& r) с той же проблемой. Финальное присваивание обычно убирается оптимизатором (RVO). Move-конструкторы вовсе не вступают в игру, за исключением может быть того, что они подменяют RVO.
Что могла бы здесь улучшить move-семантика? С умножениями ничего улучшить нельзя: им по факту неоткуда брать память, придётся выделять новую всё равно. С вычитанием немного лучше: если вы определите перегрузку operator -, принимающую rvalue ref как первый аргумент, то вы сможете отобрать у него память. Для симметрии вам нужно будет сделать ещё одну перегрузку, принимающую rvalue ref как второй аргумент. И судя по всему ещё одну с двумя rvalue ref-аргументами, иначе компилятор не сможет выбрать между предыдущими двумя, если оба аргумента — rvalue ref. То же касается и сложения.
Обратите внимание, что вам нужен достаточно серьёзный дубляж кода: все операторы должны быть объявлены по 3, а то и 4 раза.
Таким образом, мы имеем два выделения памяти и два отбора памяти у rvalue-объектов. Это лучше, чем без move-семантики, но expression templates, как вы их описали, ещё лучше: там лишнее выделение памяти вовсе не требуется.
Вывод: нет, rvalue reference'ы всё ещё не панацея.
Комментариев нет:
Отправить комментарий