#cpp #cpp11
Перехожу в одиннадцатый стандарт из старого и в нем много нового для меня, в частности
не понятно для чего в параметрах функций пишут двойной амперсанд &&. Неясно для чего
он нужен и как его можно использовать?
Вот небольшой примерчик:
void test(int && a){
a++;
// как работать с таким параметром (а) внутри функции, например вывести ее на экран?
}
int main(){
int a = 1;
test(a); // как передать переменную?
return 0;
}
Ответы
Ответ 1
Пока читаете, для себя такое небольшое правило выработал: type& - аргумент обязательно lvalue, т.е. то, что стоит слева от знака равно: int a = 1; foo(a); // a может быть изменено в foo и по выходу иметь другое значение foo(1); // ошибка компиляции const type& - аргумент может быть как lvalue (при этом даёте гарантию, что он там не поменяется) или rvalue, тогда его время жизни продлевается на время вызова: int a = 1; bar(a); // а не меняется внутри bar(2); // тоже легально type&& - аргумент может быть только rvalue, при этом внутри вызова можно его изменять и вообще, работаете как с обычной переменной (как только rvalue внутри функции обретает имя, оно "автоматом" становится lvalue :-)). Чтобы обычную переменную превратить в rvalue, нужно её "переместить", т.е. по сути, отдать владение в вызываемую функцию: int a = 1; baz(a); // ошибка компиляции baz(std::move(a)); // всё отлично, но после вызова состояние a не определено. baz(2); // тоже отлично, 2 - rvalue const type&& - такая конструкция возможна, но её смысл для меня ускальзывает. Она сродни такому: void some(const int arg); т.е. просто внутри менять запрещаете. Из правил вытекает использование: когда нужно отдать владение объектом. Возможна просто дополнительная перегрузка, чтобы можно было работать в одинаковом стиле в вызывающем коде как с rvalue, так и lvalue: void foo(int& a); // [1] void foo(int&& a); // [2] ... int a = 1; foo(a); // вызовется [1] foo(1); // вызовется [2] foo(move(a)); // вызовется [2], состояние 'a' будет неопределено.Ответ 2
Чтобы было понятнее, что именно делает &&, сравним три «классических» способа передачи аргумента (раз в вопросе фигурирует именно это применение «амперсандов»): Передача по левосторонней ссылке (Type&). Это самый простой случай; копируется только указатель на объект (чем ссылка и является на самом низком уровне). Передача по значению (Type). В этом случае компилятор создаёт полноценную копию объекта путём выделения памяти на стеке и вызова конструктора копирования. И наконец, передача по правосторонней ссылке (Type&&). Здесь компилятор также выделяет место на стеке, однако вызывает уже конструктор перемещения, задача которого — «переместить» все данные из исходного объекта в новый, превратив первый в «пустышку» (безопасную с точки зрения отсутствия связи с данными нового объекта). Почему «переместить» было написано в кавычках? Да потому, что по факту выполняется простое копирование с занулением оригинала. Следом может возникнуть вопрос: если всё сводится к простому копированию с обнулением, почему нельзя обойтись старой доброй передачей по значению? Ведь можно предположить, что простого отбрасывания данных оригинала при его уничтожении вполне достаточно. Дело в том, что это верно не всегда. Класс может содержать указатели (как, к примеру, std::vector) или дескрипторы внешних ресурсов (как std::fstream). Если выполнить просто копирование экземпляров этих классов, то мы получим: либо «глубокое» копирование всего буфера у std::vector, либо две ссылки на один и тот же файловый дескриптор у std::fstream; уничтожение одного экземпляра класса приведёт к появлению некорректного дескриптора у другого.
Комментариев нет:
Отправить комментарий