Что в C ++ означает использование операции перемещения при возврате? - PullRequest
0 голосов
/ 01 ноября 2018

Я читаю Бьярна Страуструпа Язык программирования C ++ (4-е издание) и на с. 516 он говорит:

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

Также на стр. 517 он говорит:

[Объект] имеет конструктор перемещения, так что «возврат по значению» простой и эффективный, а также "естественный"

Если return всегда использует операцию перемещения, то почему не работает что-то вроде следующего?

#include <vector>
#include <assert.h>

using namespace std;

vector<int> ident(vector<int>& v) {
    return v;
};

int main() {
    vector<int> a {};
    const vector<int>& b = ident(a);
    a.push_back(1);
    assert(a.size() == b.size());
}

Разве a и b не должны указывать на одни и те же объекты?

Ответы [ 3 ]

0 голосов
/ 01 ноября 2018

Когда функция возвращается по значению, она всегда создает новый объект. Два вектора в вашем коде отличаются по той же причине, по которой следующий код создает два вектора:

std::vector<int> v1; // create a vector
std::vector<int>& vr = v1; // create a reference to that vector, not a new vector
std::vector<int> v2 = vr; // create and copy-initialize a new vector from a reference,
// calling the copy constructor

Должно быть верно, что два вектора здесь логически эквивалентны в точке, где создается v2, что означает, что после копии их размеры и содержимое равны. Однако они являются различными векторами, и изменения одного вектора после этого не изменят другого. Вы также можете прочитать это из типов переменных; v1 - это vector, а не ссылка или указатель на vector, и, таким образом, это уникальный объект. То же самое относится к v2.

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

0 голосов
/ 01 ноября 2018

Цитирование из http://eel.is/c++draft/class.copy.elision:

В следующих контекстах инициализации копирования вместо операции копирования может использоваться операция перемещения:

(3.1) Если выражение в операторе возврата ([stmt.return]) является (возможно, в скобках) id-выражением, которое присваивает объекту имя с автоматическим продолжительность хранения , объявленная в теле или в параметре-объявлении самой внутренней функции, или лямбда-выражения, или

(3.2), если операндом выражения throw является имя энергонезависимого автоматического объекта (кроме параметра функции или catch-предложения), область которого не выходит за пределы самого внутреннего включающего блока try (если есть)

Разрешение перегрузки для выбора конструктора для копии сначала выполняется так, как если бы объект был обозначен как значение .

Следовательно, если вы возвращаете автоматическую (локальную) переменную:

vector<int> ident() {
  vector<int> v;
  return v;
};

тогда v будет в операторе return, обработанном как rvalue , и поэтому возвращаемое значение будет инициализировано как конструктор перемещения.

Однако эти критерии не соблюдаются в вашем коде, поскольку v в вашем ident не является автоматической переменной. Следовательно, он рассматривается как lvalue в операторе return, и возвращаемое значение инициализируется конструктором копирования из вектора, на который ссылается параметр функции.

Эти правила вполне естественны. Представьте, что компиляторам было разрешено переходить со всех значений l в return операторах. К счастью, они могут, только если они знают, что это lvalue будет уничтожено, что верно для автоматических переменных и параметров, передаваемых значениями в контексте операторов return.

0 голосов
/ 01 ноября 2018
vector<int> foo() {
    return ...;
};

Функция возвращает значение. Таким образом, возвращаемый объект является вновь созданным объектом. Это верно независимо от того, как создается объект: с помощью конструкции копирования, конструкции перемещения, конструкции по умолчанию или любого типа конструкции.

Вопрос построения копирования / перемещения - в основном вопрос эффективности. Если объект, из которого вы создаете, используется позже, вы ничего не можете сделать, кроме как скопировать его. Но если вы знаете, что созданный вами объект больше не используется после (как в случае с prvalues ​​или с объектом в простом операторе return), вы можете перейти от него, потому что перемещение обычно украдет от объекта переехала из. В любом случае, как я уже говорил выше, создается новый объект.

...