Как компилятор знает, как перемещать локальные переменные?

Мне интересно, как именно эта функция работает. Рассмотрим что-то вроде

std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }

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

Итак, как компилятор знает, когда переходить от выражения return?

4 ответов


существует простое правило: если условия для копирования elision выполнены (за исключением того, что переменная может быть параметром функции), обрабатывайте как rvalue. Если это не удается, обращайтесь как lvalue. В противном случае рассматривайте как lvalue.

§12.8 [class.copy] p32

когда критерии выбора операции копирования выполнены или будут выполнены, за исключением того, что исходный объект является параметром функции,и объект, котор нужно скопировать обозначен lvalue, разрешением перегрузки для того чтобы выбрать конструктор для копии сначала выполняется так, как если бы объект был обозначен rvalue. Если разрешение перегрузки не удается или если тип первого параметра выбранного конструктора не является ссылкой rvalue на тип объекта (возможно, CV-qualified), разрешение перегрузки выполняется снова, рассматривая объект как lvalue. [ Примечание: это двухступенчатое разрешение перегрузки должно выполняться независимо от того, произойдет ли копирование elision. Он определяет конструктор вызывается, если elision не выполняется, и выбранный конструктор должен быть доступен, даже если вызов отменен. -конец Примечания ]

пример:

template<class T>
T f(T v, bool b){
  T t;
  if(b)
    return t; // automatic move
  return v; // automatic move, even though it's a parameter
}

не то, чтобы я лично согласен с этим правилом, так как нет автоматического перемещения в следующем коде:

template<class T>
struct X{
  T v;
};

template<class T>
T f(){
  X<T> x;
  return x.v; // no automatic move, needs 'std::move'
}

см. также этот вопрос.


стандарт не говорит "ссылается на локальную переменную", но он говорит"это имя энергонезависимого автоматического объекта".

§12.8 [class.copy] p31

[...] Это elision операций копирования / перемещения, называется копировать elision, допускается при следующих обстоятельствах [...]:

  • на return оператор в функции с типом возвращаемого класса,когда выражение является именем энергонезависимой автоматический объект [...]

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

struct A {
  std::unique_ptr<int> x;
  std::unique_ptr<int> f() { return x; }
};

int main() { A a; a.f(); }

в этом случае выражение return - это имя переменной с автоматической продолжительностью хранения. Некоторые другие пункты стандарта могут быть истолкованы несколькими способами, но правило состоит в том, чтобы принять интерпретацию, которая скорее всего, намеренно.


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

только если возврат упоминает явно переменная, если вы возвращаете указатель или ссылку на локальную переменную, ей не нужно этого делать:
std::unique_ptr<int>& same( std::unique_ptr<int>& x ) { return x; }
std::unique_ptr<int> foo() {
   std::unique_ptr<int> p( new int );
   std::unique_ptr<int>& r = same(p);
   return r;                           // FAIL
}

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

Если вы напрямую возвращаете локальную переменную, то задание простое: вы можете ее переместить.

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

посмотреть вот некоторые примеры.