2017-02-10 6 views
18

Скажем, у меня есть следующий тип:Гарантированный элизия и приковали вызовы функций

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; 

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

ответ

19

Это будет выполнено с 1 копией и 3-мя ходовыми конструкциями.

  1. Составьте копию a, чтобы связаться с lhs.
  2. Переместить конструкцию lhs из первых +.
  3. Возврат первого + будет привязан к значению lhs параметр второго + с разрешением.
  4. Возврат второго lhs приведет к второму движению.
  5. Возврат третьего lhs приведет к третьему движению.
  6. Временное возвращение с третьего + будет построено по адресу sum.

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

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

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

в результате 0 копий и 4 ходов (7 ходов с -fno-elide-constructors).

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


Update

Если вы заинтересованы в различные способы оптимизации этого, вы могли бы начать с перегрузкой и 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 не это делать).

+0

Так не цепочки элиции? например построив возврат 'a + b' непосредственно в' operator + (??, c) '? – Barry

+2

Я даже не думаю, что законно возвращать «lhs». Однако я также не думаю, что у кого-либо из авторов компилятора была какая-то мотивация сделать его законным (никто его не пытается). –

+0

Почему это может быть незаконным? Может ли это стать законным в будущем? – Orient

9

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

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

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

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

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

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

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


Ваш вопрос действительно, кажется ли параметры остаются исключены из элизии в C++ 17. Потому что they were already excluded in prior versions. И это не изменится; причины исключения параметров из элиции не были признаны недействительными.

«Гарантированное разрешение» применимо только к к prvalues. Если у него есть имя, то не может быть prvalue.

+0

Некоторые из них будут двигаться, хотя - вы имеете в виду 1 копию и 3 хода? – Barry

+1

Чтобы поместить комментарий Nicol относительно копии параметра по-разному: принятие аргумента по значению помогает только для копирования elision на пути и * только * имеет смысл, когда * потребляет * параметр, т. Е. 'Std :: move()' ing it в другом месте, например: 'X rc (std :: move (lhs)); rc + = rhs; return rc; ' –

+0

@Barry: Да, я забыл, что переходы из возвращаемых локальных переменных являются автоматическими. –