Перемещение¶
В Ü есть возможность в некоторых случаях заменить затратные операции копирования операциями перемещения. Перемещение - это смена местоположения объекта в адресном пространстве без смены содержимого объекта.
Перемещение происходит в тех случаях, когда перемещаемый объект существует здесь и сейчас в единственном экземпляре. Объект, существующий здесь и сейчас, создаётся через конструирование объекта или как результат вызова функции. При перемещении происходит опциональный вызов деструктора для назначения (если там уже есть объект) и простом побайтовом копировании содержимого перемещаемого объекта в место назначения.
Перемещение может произойти в следующих случаях:
- Передаче аргумента-значения в функцию
- Присваивании
- Инициализации
- Возврате значения
struct S{ i32 x = 0; }
fn GetS() : S
{
// Создаётся временный объект типа "S", после чего он перемещается в возвращаемое значение.
return S();
}
fn TakeS( S s ){}
fn Foo()
{
// Создаётся временный объект типа "S", после чего он перемещается в переменную "s0" при её инициализации.
auto s0= S();
// Временное значение, полученное в результате вызова функции, перемещается при инициализации в переменную "s1".
var S s1(GetS());
var S mut s2;
// Создаётся временный объект типа "S", после чего он перемещается в переменную "s2". Перед перемещением вызывается деструктор для "s2".
s2= S();
// Создаётся временный объект типа "S", после чего он перемещается в аргумент функции.
TakeS( S() );
}
Оператор «move»¶
Перемещение происходит только в случаях, когда объект существует здесь и сейчас.
Если же объект является стековой переменной или её частью, аргументом функции и т. д. он просто так перемещаться не будет.
Но существует способ заставить его переместиться - оператор move
.
Оператор move
принимает в качестве аргумента имя локальной переменной или аргумента функции ( в т. ч. byval
this
) и возвращает значение, существующее здесь и сейчас.
Перемещённая переменная при этом считается уничтоженной и перестаёт быть доступной. Деструктор для перемещённой переменной вызываться не будет.
struct S{ i32 x = 0; }
fn TakeS( S s ){}
fn Foo( S mut s_arg )
{
var S mut s0, mut s1;
// Произойдёт копирование локальной переменной "s0"
TakeS( s0 );
// Произойдёт перемещение локальной переменной "s1".
TakeS( move(s1) );
// Произойдёт перемещение аргумента функции.
TakeS( move(s_arg) );
// Деструктор вызовется только для переменной "s0", но не для переменных "s1" и "s_arg".
}
У оператора перемещения есть ряд ограничений. Перемещать можно только изменяемые локальные переменные и аргументы функций.
Нельзя переместить ссылки, члены композитных локальных переменных, неизменяемые локальные переменные.
Нельзя перемещать внутри цикла внешнюю по отношению к циклу переменную. Нельзя условно перемещать переменную - перемещение должно происходить во всех ветвях одного if-else
.
Оператор «take»¶
Данный оператор отчасти похож на оператор move
, но имеет ряд различий. Он принимает любое выражение, не только имя переменной.
Результат этого выражения он перемещает, конструируя в месте перемещения значение по умолчанию. Если тип перемещаемого значения не конструируем по умолчанию, будет порождена ошибка.
struct S
{
i32 x;
fn constructor() ( x= 0 ) {}
fn constructor( i32 in_x ) ( x= in_x ) {}
}
fn Foo()
{
var [ S, 3 ] mut arr[ (55), (77), (99) ];
var S s= take(arr[1]); // Значение в "arr[1]" будет перемещено в новую переменную "s". Для "arr[1]" будет вызван конструктор по умолчанию.
}
Перемещение при возврате¶
При возврате локальных переменных и аргументов функций происходит их автоматическое перемещение.
Но это работает, только если в операторе return
указано непосредственно имя переменной, а не какое-либо сложное выражение или ссылка и только если на указанную переменную не существует живых ссылок.
struct S
{
fn constructor( mut this, S& other )= delete;
i32 x;
}
static_assert( !typeinfo</S/>.is_copy_constructible );
fn MakeS() : S
{
var S s{ .x= 123 };
return s; // Произойдёт перемещение, а не копирование.
}