Издержки оператора массива C ++

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

// edit: yes the parameters where meant to be passed by reference
//       and maintain constness sorry !
template< typename T >
std::vector<T> operator+( const std::vector<T>& a, const std::vector<T>& b )
{
assert( a.size() == b.size() );
std::vector<T> o; o.reserve( a.size() );
for( std::vector<T>::size_type i = 0; i < a.size(); ++i )
o[i] = a[i] + b[i];
return o;
}

// same for operator* but a[i] * b[i] instead

std::vector<double> a, b, c, d, e;

// do some initialization of the vectors

e = a * b + c * d

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

Что это за техника?

1

Решение

Как @Agnew упомянул очень рано, Техника, которую вы описываете шаблоны выражений.

Обычно это делается с помощью математической концепции вектора1, не std::vector,
Широкие штрихи:

  1. Не делайте математических операций над вашими векторами, чтобы вернуть результат. Вместо этого пусть они вернут объект прокси это представляет операцию, которая в конечном счете должна быть сделана. a * b может вернуть объект «прокси-умножения», который просто содержит константные ссылки на два вектора, которые должны быть умножены.

  2. Напишите математические операции для этих прокси-серверов, что позволит объединить их в цепочку, поэтому a * b + c * d становится (TempMulProxy) + (TempMulProxy) становится (TempAddProxy), все без какой-либо математики или копирования каких-либо векторов.

  3. Напишите оператор присваивания, который принимает ваш прокси-объект для правого объекта. Этот оператор может видеть все выражение a * b + c * d и сделайте эту операцию эффективно на вашем векторе, зная при этом пункт назначения. Все без создания нескольких временных векторных объектов.

1 или матрица или кватернион и т.д … *

3

Другие решения

Я не вижу здесь вопроса. Тем не менее, мой хрустальный шар говорит мне, что вы хотите знать лучший метод из двух методов, которые вы придумали для выполнения компонентных арифметических операций над векторами, такими как a * b + c * d где a, b, c, d векторы (std::vector<T>) имеющие одинаковый размер:

  1. Для каждой операции необходимо выполнить цикл по элементам, выполнить вычисление и вернуть результирующий вектор. Объедините эти операции в формулу для векторов.

  2. Для каждого элемента во входных векторах вычислите целое выражение и запишите его в один конечный результирующий вектор.

Есть две вещи для рассмотрения:

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

Тем не менее, есть хороший вариант для реализации второго варианта, который выглядит очень красиво:

std::vector<int> a, b, c, d, e;
// fill a, b, c, d with data

auto expression = [](int a, int b, int c, int d){ return a * b + c * d; };

assert (a.size() == b.size() && b.size() == c.size() && c.size() == d.size());
e.reserve(a.size());

for(auto _a = a.begin(), _b = b.begin(), _c = c.begin(), _d = d.begin(), _e = e.begin();
_a != a.end();
++_a, ++_b, ++_c, ++_d, ++_e)
{
*_e = expression(*_a, *_b, *_c, *_d);
}

Таким образом, вы можете отделить выражение от логики, чтобы оценить его:

void componentWise4(std::function<int(int,int,int,int)> f,
const std::vector<int> & a,
const std::vector<int> & b,
const std::vector<int> & c,
const std::vector<int> & d,
std::vector<int> & result)
{
assert (a.size() == b.size() && b.size() == c.size() && c.size() == d.size());
result.reserve(a.size());

for(auto _a = a.begin(), _b = b.begin(), _c = c.begin(), _d = d.begin(), _result = result.begin();
_a != a.end();
++_a, ++_b, ++_c, ++_d, ++_result)
{
*_result = expression(*_a, *_b, *_c, *_d);
}
}

Который затем называется так:

std::vector<int> a, b, c, d, e;
// fill a, b, c, d with data

componentWise4([](int a, int b, int c, int d){ return a * b + c * d; },
a, b, c, d, e);

Я уверен, что этот «оценщик выражений» может быть расширен с помощью новой функции C ++ 11 «переменные шаблоны» для поддержки произвольного числа аргументов в выражении, а также различных типов. Мне не удалось заставить его работать (переменная вещь шаблона), вы можете попытаться закончить мою попытку здесь: http://ideone.com/w88kuG (Я новичок в вариационных шаблонах, поэтому не знаю синтаксис).

1

Что вы хотите в «Язык программирования C ++». Третье издание Бьярна Страуструпа в 22.4.7 Временные, копирование и циклы [num.matrix]. Это всегда хорошая идея, чтобы прочитать книгу.

Если у вас его нет, у нас есть два варианта:

Во-первых: мы пишем набор функций для прямого вычисления некоторых наиболее ожидаемых комбинаций (например, mul_add_and_assign (&U,&M,&В,&Ж) рассчитать U = M * V + W) и побудить пользователя выбрать себе, какая функция ему наиболее удобна.

Второе: мы можем ввести некоторые вспомогательные классы (например, VxV, VplusVи т. д.) которые содержат только ссылку на аргументы каждой операции и определяют преобразование оператора в vector, Теперь мы создаем перегрузки операторов + а также * которые берут два вектора по ссылке и просто возвращают объект соответствующего типа. Мы можем создавать классы типа VxVplusVxV рассчитать более сложные операции. Теперь мы можем перегрузить operator= в задницу VxVplusVxV к vector, И в этой последней перегрузке мы сделали все вычисления, используя ссылки на аргументы, хранящиеся в объектах вспомогательных классов, без создания или минимального создания временных векторов.

1
По вопросам рекламы [email protected]