Гарантированные звонки с использованием функций и цепочек

Скажем, у меня есть следующий тип:

struct X {
    X& operator+=(X const&);
    friend X operator+(X lhs, X const& rhs) {
        lhs += rhs;
        return lhs;
    }
};

И у меня есть объявление (предположим, что все именованные переменные являются lvalues ​​типа X):

X sum = a + b + c + d;

В С++ 17, какие у меня есть гарантии о том, сколько копий и перемещает это выражение? Как насчет не гарантированного разрешения?

Ответы

Ответ 1

Это выполнит 1 конструкцию копии и 3 перемещения.

  • Сделайте копию a для привязки к lhs.
  • Переместить конструкцию lhs из первого +.
  • Возврат первого + будет привязан к параметру lhs по значению второго + с разрешением.
  • Возврат второго lhs приведет к второй конструкции перемещения.
  • Возврат третьего lhs приведет к третьей конструкции перемещения.
  • Временное, возвращаемое с третьего +, будет построено в sum.

Для каждой из описанных выше перемещающих конструкций существует другая конструкция перемещения, которая необязательно устранена. Таким образом, у вас есть только 1 копия и 6 ходов. Но на практике, если вы не -fno-elide-constructors, у вас будет 1 копия и 3 хода.

Если вы не ссылаетесь на a после этого выражения, вы можете продолжить оптимизацию с помощью:

X sum = std::move(a) + b + c + d;

что приводит к 0 копиям и 4 ходам (7 движений с -fno-elide-constructors).

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


Обновление

Если вас интересуют различные способы оптимизации этого, вы можете начать с перегрузки lhs на X const& и X&&:

friend X operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}

Это уменьшает до 1 копии и 2 хода. Если вы хотите ограничить доступ своих клиентов к возврату вашего + по ссылке, вы можете вернуть X&& из одной из перегрузок, как это:

friend X&& operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}

Доведение до 1 копии и 1 переход. Обратите внимание, что в этом последнем дизайне, если вы это делаете, клиент:

X&& x = a + b + c;

то X - это болтающаяся ссылка (поэтому std::string этого не делает).

Ответ 2

ОК, начнем с этого:

X operator+(X lhs, X const& rhs) {
    lhs += rhs;
    return lhs;
}

Это всегда вызовет копирование/переход из параметра в объект возвращаемого значения. С++ 17 не изменяет этого, и никакая форма исключения не может избежать этой копии.

Теперь рассмотрим одну часть вашего выражения: a + b. Поскольку первый параметр operator+ берется значением, в него должен быть скопирован a. Так что один экземпляр. Возвращаемое значение будет скопировано в возвращаемое значение prvalue. Так что 1 копия и одно перемещение/копирование.

Теперь, следующая часть: (a + b) + c.

С++ 17 означает, что значение prvalue, возвращаемое из a + b, будет использоваться для непосредственной инициализации параметра operator+. Это не требует копирования/перемещения. Но возвращаемое значение из этого будет скопировано из этого параметра. Так что 1 копия и 2 перемещения/копии.

Повторите это для последнего выражения, и это 1 копия и 3 перемещения/копии. sum будет инициализирован из выражения prvalue, поэтому здесь не нужно копировать.


На ваш вопрос действительно кажется, что параметры остаются исключенными из elision в С++ 17. Поскольку они были исключены в предыдущих версиях. И это не изменится; причины исключения параметров из элиции не были аннулированы.

"Гарантированное исключение" применимо только к значениям pr. Если у него есть имя, это не может быть prvalue.