Будут ли компиляторы автоматически применять семантику перемещения в методе установки?

Я хочу знать, разрешено ли компилятору автоматически использовать конструктор перемещения для wstring в следующем методе установки (без явного вызова std::move):

void SetString(std::wstring str)
{
    m_str = str;  // Will str be moved into m_str automatically or is std::move(str) needed?
}

Из того, что я прочитал, звучит так, будто компилятору не разрешено принимать это решение, поскольку str является lvalue, но кажется довольно очевидным, что использование перемещения здесь не изменит поведение программы.

Запрещая перемещение, будет ли применяться какой-либо другой вид удаления копии?


person FrolickingFerret    schedule 21.03.2013    source источник
comment
Можно было бы представить, что в C++ есть правило исключения копирования, которое разрешает эту оптимизацию, но это не так.   -  person David Schwartz    schedule 21.03.2013
comment
А что, если SetString встроенный??   -  person qPCR4vir    schedule 21.03.2013


Ответы (3)


[является] компилятором [...] разрешено автоматически использовать конструктор перемещения

Да, было бы неплохо. Но это не только оптимизация, это реально влияет на язык.

Рассмотрим тип только для перемещения, такой как unique_ptr:

std::unique_ptr<int> f()
{
  std::unique_ptr<int> up;
  return up; // this is ok although unique_ptr is non-copyable.
}

Предположим, ваше правило будет включено в стандарт C++, называемое правилом «последнего вхождения аргумента».

void SetString(std::unique_ptr<int> data)
{
    m_data = data; // this must be ok because this is "argument's last occurence"
}

Проверить, используется ли идентификатор в возврате, легко. Проверка, является ли это «последним появлением аргумента», не является.

void SetString(std::unique_ptr<int> data)
{
    if (condition) {
      m_data = data; // this is argument's last occurence
    } else {
      data.foo();
      m_data = data; // this is argument's last occurence too
    }
    // many lines of code without access to data
}

Это тоже допустимый код. Таким образом, каждый компилятор должен будет проверять «последнее вхождение аргумента», что не так просто. Для этого ему пришлось бы просмотреть всю функцию, чтобы решить, верна ли первая строка. Также трудно рассуждать как человек, если вам нужно прокрутить 2 страницы вниз, чтобы проверить это.

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

person ipc    schedule 21.03.2013
comment
Спасибо, ребята, мое любопытство было удовлетворено - похоже, здесь требуется использование явного вызова std::move, чтобы компилятор выполнил оптимизацию. Я также нашел stackoverflow.com/questions/6089425/ вскоре после публикации, что отвечает на мой вопрос в более общем плане. - person FrolickingFerret; 22.03.2013

нет, здесь не будет использоваться семантика перемещения, так как str можно использовать в следующем коде, на самом деле, даже если бы это было rvalue, вам все равно придется std::move заставить его.. если вы хотите использовать семантику перемещения, я бы посоветовал получить wstring&& str в функцию, а затем с помощью move..

person Alon    schedule 21.03.2013
comment
А что, если SetString встроенный?? - person qPCR4vir; 21.03.2013
comment
Рекомендация ложная. Функция должна принимать по значению, а не по rvalue-reference. Если вы берете rvalue-reference, вам нужно предоставить другую точно такую ​​же реализацию для lvalue - person David Rodríguez - dribeas; 21.03.2013
comment
Если это не именно то, что вы хотите, принудительно использовать эту функцию только по rvalue, а также это дает вам возможность иногда копировать, а иногда и не копировать, если у вас есть перегрузка функций, плохое копирование - person Alon; 23.03.2013

Нет, компилятор не разрешен. По некоторым причинам, не только потому, что это сложно сделать. Я думаю, что копирование и перемещение могут иметь побочные эффекты, и вам нужно знать, когда вы можете ожидать, что каждый из них будет использован. Например, хорошо известно, что возврат локального объекта приведет к его перемещению - вы ожидаете, что это задокументировано, это нормально.

Итак, у нас есть следующие возможности:

Ваш пример:

void SetString(std::wstring str)
{
     m_str = str;  
}

Для значений r: одна ссылка r в str плюс копия в m_str. Для l-значений: копия в str и копия в m_str.

Мы можем сделать это «лучше» вручную:

void SetString( std::wstring str)
{
     m_str = std::move(str);  
}

Для значений r: один r-ref в str плюс переход в m_str. Для L-значений: копия в str и перемещение в m_str.

Если по какой-то причине (вы хотите, чтобы он компилировался без C++11 без изменений, но автоматически использовал преимущества C++11 при переносе кода?) вы не хотите «оптимизировать вручную» код, вы можете сделать:

void SetString(const std::wstring& str)
{
     m_str = str;  
}

Для значений r: одна ссылка в str плюс копия в m_str. Для L-значений: ссылка в str и копия в m_str. Никогда 2 копии.

person qPCR4vir    schedule 21.03.2013
comment
с передачей по значению + std::move это будет только один ход, который намного дешевле, чем одна копия. - person ipc; 21.03.2013
comment
@ipc. Я не уверен, что понимаю тебя. передать по значению = скопировать? плюс переезд. Или вы имеете в виду, что функция принимает строку && str, а затем у нас есть 2 хода? И да, я согласен, что мы можем вручную установить какой-то ход, который компилятор не может сделать самостоятельно. Лучшее, что я вижу, это 2 перемещения, если вы передаете значение rvalue (временное?) в функцию и 2 раза перемещаете его, чтобы иметь копию в m_str. Думаю, лучшее, что мы можем сделать, это аналог передачи необработанного указателя. Я знаю, что могу ошибаться. - person qPCR4vir; 21.03.2013
comment
Это: SetString(std::move(wstring("temp"))); будет не более 2 ходов, если будет передано по значению. - person ipc; 22.03.2013
comment
@ipc Теперь я согласен с вами: 2 хода, а не только один ход ... И вам нужно вручную установить ход внутри функции, и я согласен, что это самый эффективный код. Но если вы не хотите (почему?), передача const ref, наконец, так же эффективна, а для rvalue лучше, чем передача по значению. Или нет? - person qPCR4vir; 22.03.2013
comment
Нет. :-) Компилятору разрешено оптимизировать копию при передаче по значению и перемещать temp непосредственно в цель. - person Bo Persson; 22.03.2013
comment
@Бо Перссон. На какую цель: str или m_str ? - person qPCR4vir; 22.03.2013
comment
@qpc — при передаче временного значения по значению нет необходимости создавать копию для параметра. Если у вас есть m_str = std::move(str); внутри функции, это может быть единственным необходимым ходом. - person Bo Persson; 22.03.2013
comment
@BoPersson Является ли перемещение временного файла непосредственно к цели более эффективным, чем передача ссылки? - person qPCR4vir; 22.03.2013
comment
@qpc - проблема в том, что если у вас есть ссылка const, вы не можете переместить значение, вам нужно скопировать (всегда). - person Bo Persson; 22.03.2013
comment
@БоПерссон. Ок спасибо, понял: передаем r-ref, а потом делаем ход (вручную внутри функции). Верно? - person qPCR4vir; 22.03.2013
comment
@БоПерссон. Не могли бы вы проверить, получил ли я, наконец, правду? - person qPCR4vir; 22.03.2013
comment
@ipc. Не могли бы вы проверить, получил ли я, наконец, правду? Я отредактировал ответ. - person qPCR4vir; 22.03.2013
comment
@qPCR4vir: ни один из них не лучше другого, у обоих есть компромиссы. Обычно я использую либо один std::wstring, либо пару std::wstring&&/std::wstring const&. Но std::wstring, вероятно, предпочтительнее одного std::wstring const&, если вы позже скопируете его (не всегда, например, если вы используете шаблон clone()). - person ipc; 22.03.2013