Как добиться «оптимального» разрешения перегрузки оператора в арифметических выражениях с r-значениями? - PullRequest
5 голосов
/ 25 февраля 2011

Прежде всего, я прошу прощения за слишком многословный вопрос.Я не мог придумать какой-либо другой способ точно подвести итог моей проблемы ... Теперь перейдем к актуальному вопросу:

В настоящее время я экспериментирую с ссылками на значения C ++ 0x ... Следующий код создает нежелательныеповедение:

#include <iostream>
#include <utility>

struct Vector4
{
    float x, y, z, w;

    inline Vector4 operator + (const Vector4& other) const
    {
        Vector4 r;
        std::cout << "constructing new temporary to store result"
                  << std::endl;
        r.x = x + other.x;
        r.y = y + other.y;
        r.z = z + other.z;
        r.w = w + other.w;
        return r;
    }
    Vector4&& operator + (Vector4&& other) const
    {
        std::cout << "reusing temporary 2nd operand to store result"
                  << std::endl;
        other.x += x;
        other.y += y;
        other.z += z;
        other.w += w;
        return std::move(other);
    }
    friend inline Vector4&& operator + (Vector4&& v1, const Vector4& v2)
    {
        std::cout << "reusing temporary 1st operand to store result"
                  << std::endl;
        v1.x += v2.x;
        v1.y += v2.y;
        v1.z += v2.z;
        v1.w += v2.w;
        return std::move(v1);
    }
};

int main (void)
{
    Vector4 r,
            v1 = {1.0f, 1.0f, 1.0f, 1.0f},
            v2 = {2.0f, 2.0f, 2.0f, 2.0f},
            v3 = {3.0f, 3.0f, 3.0f, 3.0f},
            v4 = {4.0f, 4.0f, 4.0f, 4.0f},
            v5 = {5.0f, 5.0f, 5.0f, 5.0f};

    ///////////////////////////
    // RELEVANT LINE HERE!!! //
    ///////////////////////////
    r = v1 + v2 + (v3 + v4) + v5;

    return 0;
}

приводит к выводу

, создающему новый временный объект для сохранения результатапостроение нового временного для хранения результатаповторное использование временного первого операнда для сохранения результатаповторное использование временного 1-го операнда для сохранения результата

, в то время как я надеялся, что что-то вроде

создаст новый временный объект для хранения результатаповторное использование временного первого операнда для сохранения результатаповторное использование временного второго операнда для сохранения результатаповторное использование временного второго операнда для сохранения результата

После попытки воспроизвести то, что делал компилятор (я использую MinGW G ++ 4.5.2 с опцией -std = c ++ 0x на случай, если это имеет значение), это на самом деле кажется вполне логичным.Стандарт гласит, что арифметические операции одинакового приоритета оцениваются / группируются слева направо (почему я предположил, что справа налево я не знаю, я думаю, это более интуитивно для меня).Итак, что здесь произошло, так это то, что компилятор сначала оценил подвыражение (v3 + v4) (поскольку оно в скобках?), А затем начал сопоставлять операции в выражении слева направо с перегрузками операторов, что привело к вызову Vector4 operator + (const Vector4& other) для подвыражения v1 + v2.Если я хочу избежать ненужного временного объекта, я должен убедиться, что слева от любого вложенного выражения в скобках появляется не более одного операнда lvalue, что нелогично для любого, кто использует эту «библиотеку» и невинно ожидаетоптимальная производительность (как при минимизации создания временных).

(я знаю, что в моем коде есть неоднозначность относительно operator + (Vector4&& v1, const Vector4& v2) и operator + (Vector4&& other), когда (v3 + v4) должен быть добавлен к результату v1 + v2, что приводит к предупреждению. Но в моем случае это безопасно, и я не хочу добавлять еще одну перегрузку для двух опорных операндов rvalue - кто-нибудь знает, есть ли способ отключить это предупреждение в gcc?)

* 1028Короче говоря, мой вопрос сводится к следующему: существует ли какой-либо способ или шаблон (предпочтительно независимый от компилятора), этот векторный класс можно переписать, чтобы разрешить произвольное использование скобок в выражениях, которое все еще приводит к «оптимальному» выбору перегрузок операторов (оптимальный с точки зрения «производительности», то есть максимизация привязки к rvalуе ссылки)?Возможно, я прошу слишком много, и это невозможно ... если так, то это тоже хорошо.Я просто хочу убедиться, что я ничего не пропустил.

Большое спасибо заранее

Приложение

Во-первых, благодаря быстрым ответам, которые я получил, через несколько минут (!) - я действительно должен был начать публиковать здесь раньше ...

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

Вы можете заметить, что я не держу в классе никаких ресурсов, таких как кучная память.Его члены - только скалярные типы.На первый взгляд это делает его подозреваемым кандидатом на оптимизацию на основе семантики перемещения (см. Также этот вопрос, который действительно помог мне понять концепции, лежащие в основе ссылок на rvalue).

Однако, посколькуклассы, для которых предполагается, что это прототип, будут использоваться в контексте, критически важном для производительности (точнее, в 3D-движке), я хочу оптимизировать все, что только возможно.Алгоритмы с низкой сложностью и математические методы, такие как справочные таблицы, должны, конечно, составлять основную часть оптимизаций, поскольку все остальное будет просто устранять симптомы, а не устранять истинную причину плохой производительности.Я хорошо это знаю.

Учитывая это, я намерен оптимизировать алгебраические выражения с помощью векторов и матриц, которые по сути являются структурами простых старых данных без указателей на данные в них (в основном из-за недостатков производительности). вы получаете данные из кучи [необходимость разыменования дополнительных указателей, соображений кэширования и т. д.]).

Меня не волнует назначение-перемещение или конструкция, я просто не хочу, чтобы во время вычисления сложного алгебраического выражения создавалось больше временных значений, чем это абсолютно необходимо (обычно только один или два, например, матрица и вектор) .

Это мои мысли, которые могут быть ошибочными. Если да, поправьте меня:

  1. Чтобы достичь этого, не полагаясь на RVO, необходим возврат по ссылке (опять же: учтите, у меня нет удаленных ресурсов, только члены со скалярными данными).
  2. Возвращение по ссылке делает выражение вызова функции lvalue, подразумевая, что возвращаемый объект не является временным, что плохо, но возврат по ссылке rvalue делает выражение вызова функции xvalue (см. 3.10.1), что хорошо в контексте моего подхода (см. 4)
  3. Возвращение по ссылке опасно из-за возможного короткого срока службы объектов, но:
  4. Временные гарантированно будут жить до конца оценки выражения, в котором они были созданы, поэтому:
  5. делает безопасным возврат по ссылке от тех операторов, которые принимают по крайней мере одну ссылку на rvalue в качестве аргумента, если объект, на который ссылается этот аргумент ссылки на rvalue, является объектом, возвращаемым ссылкой. Поэтому:
  6. Любое произвольное выражение, использующее только двоичные операторы, может быть оценено путем создания только одного временного, когда задействовано не более одного PoD-подобного типа, а двоичные операции не требуют временного характера (например, умножение матриц)

(Еще одна причина возврата по rvalue -ссылке заключается в том, что она ведет себя как возврат по значению в терминах rvalue-ness выражения вызова функции; и это требуется для выражения вызова оператора / функции быть rvalue для привязки к последующим вызовам к операторам, которые принимают ссылки на rvalue.Как указано в (2), вызовы функций, которые возвращаются по ссылке, являются lvalues ​​и поэтому будут привязываться к операторам с подписью T operator+(const T&, const T&), что приводит к создание ненужного временного)

Я мог бы достичь желаемой производительности, используя подход в стиле C таких функций, как add(Vector4 *result, Vector4 *v1, Vector4 *v2), но давайте, мы живем в 21 веке ...

Итак, моя цель - создать векторный класс, который достигает той же производительности, что и C-подход, используя перегруженные операторы. Если это само по себе невозможно, то, думаю, ничего не поделаешь. Но я был бы признателен, если бы кто-нибудь мог объяснить мне, почему мой подход обречен на провал (конечно, проблема оценки оператора слева направо, которая была первоначальной причиной моего поста в стороне).
На самом деле, я использовал «настоящий» векторный класс, который пока является упрощением без каких-либо сбоев или поврежденной памяти. И на самом деле, я никогда не возвращаю локальные объекты в качестве ссылок, поэтому проблем быть не должно. Смею сказать, что то, что я делаю, соответствует стандартам.

Любая помощь по первому вопросу, конечно, будет цениться!

большое спасибо за все терпение снова

Ответы [ 2 ]

1 голос
/ 25 февраля 2011

Я рекомендую смоделировать ваш код после оператора basic_string + (), найденного в главе 21 N3225 .

1 голос
/ 25 февраля 2011

Вы не должны возвращать ссылку на rvalue, вы должны возвращать значение. Кроме того, вы не должны указывать как член , так и свободный оператор +. Я поражен, что даже скомпилирован.

Edit:

r = v1 + v2 + (v3 + v4) + v5;

Как вы могли возможно иметь только одно временное значение, когда выполняете два суб-вычисления? Это просто невозможно. Вы не можете переписать Стандарт и изменить его.

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

...