Эффективный функциональный стиль (C ++ 11 нормально) - PullRequest
0 голосов
/ 29 февраля 2012

Допустим, у меня есть следующее:

struct point
{
  double x;
  double y;
  double z;
};

Я могу написать следующее:

void point_mult(point& p, double c) { p.x *= c; p.y *= c; p.z *= c; }
void point_add(point& p, const point& p2) { p.x += p2.x; p.y += p2.y; p.z += p2.z; }

Поэтому я могу сделать следующее:

point p{1,2,3};
point_mult(p, 2);
point_add(p, point{4,5,6});

Для этого не требуется копий point, а только две конструкции, а именно конструкция point{1,2,3} и конструкция point{4,5,6}.Я считаю, что это применимо, даже если point_add, point_mult и point{...} находятся в отдельных единицах компиляции (т.е. не могут быть встроены).

Однако я хотел бы написать код в более функциональном стиле, подобном следующему:

point p = point_add(point_mult(point{1,2,3}, 2), point{4,5,6});

Как можно написать point_mult и point_add так, чтобы копии не требовались (даже если point_mult и point_add находятся в отдельных блоках компиляции), или функциональный стиль вынужден быть не таким эффективным в C ++?

Ответы [ 5 ]

4 голосов
/ 29 февраля 2012

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

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

point &&point_mult(point &&p, double c);

Конечно, это будет связано только с временными.Таким образом, вам потребуется альтернативная версия для l-значений:

point &point_mult(point &p, double c);

Дело в том, что вы пропускаете ссылки как они есть, либо как ссылки на временные ссылки, либо как ссылки на l-значения.

3 голосов
/ 29 февраля 2012

Это можно сделать с помощью очень уродливого шаблонного метапрограммирования. Например, eigen использует шаблоны, поэтому такие выражения, как matrix1 + matrix2 * matrix3, не нуждаются в создании временных. Суть того, как это работает, заключается в том, что операторы + и * для матриц не возвращают Matrix объекты, а вместо этого возвращают какой-то объект матричного выражения, который шаблонизируется по типам параметров-выражений. Этот объект выражения матрицы может затем вычислять части выражения только тогда, когда они необходимы, вместо создания временных объектов для хранения результата подвыражений.

На самом деле реализация этого может быть довольно грязной. Посмотрите на источник Эйгена, если вам интересно. Boost's uBlas также делает нечто подобное, хотя и не так широко, как eigen.

2 голосов
/ 29 февраля 2012

Во-первых, почему бы не использовать «лучшие» функции?

struct Point {
  double x;
  double y;
  double z;

  Point& operator+=(Point const& right) {
    x += right.x; y += right.y; z += right.z;
    return *this;
  }

  Point& operator*=(double f) {
    x *= f; y *= f; z *= f;
    return *this;
  }
};

Теперь его можно использовать как :

Point p = ((Point{1,2,3} *= 2) += Point{4,5,6});

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

  1. Сделайте это
  2. Сделайте это быстро

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

2 голосов
/ 29 февраля 2012

Эффективной (и обобщенной) техникой является шаблоны выражений . Вы можете прочитать хорошее вводное объяснение здесь .

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

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

0 голосов
/ 29 февраля 2012

Изменить определение point_mult() на:

point& point_mult(point& p, double c) { p.x *= c; p.y *= c; p.z *= c; return p; }
^^^^^^                                                                ^^^^^^^^^

И назовите это как:

point & p = point_add(point_mult(*new point{1,2,3}, 2), point{4,5,6});
     ^^^                         ^^^^^

нет копии . Однако позже вы должны сделать delete &p; для освобождения памяти.

...