#cpp
В одной из книжек по С++ прочитал такое: double a = 8.1; double && b = 3.2; double && c = 3*a-4; double && d = func(c); В чем разница между 1 и 2 строкой? С 3-ей ясно: после вычисления выражения мы получим число, которое будет скопировано, если записать так: double c = 3*a-4; Используя && мы предотвращаем копирования. 4-ая строка подобная 3: после выполнения функции произойдет копирования, если убрать &&.
Ответы
Ответ 1
Разница между 1-й и 2-й строками в том, что в первой строке создаётся переменная типа double, а во второй создаётся rvalue-ссылка на временный объект типа double. В данном случае время жизни этого временного объекта продлевается на время жизни ссылки. Этот пример не интересен и поэтому его сложно понять. Давайте разберём более интересный пример. Пусть у нас есть следующий код: #includeclass A { public: A() = default; A(const A& other) { std::cout << "Copy ctor\n"; } A(A&& other) { std::cout << "Move ctor\n"; } }; void first(A a) { //... } void second(A&& a) { //... } int main() { first(A{}); std::cout << "Let's call the second:\n"; second(A{}); return 0; } Он выведет следующее: Move ctor Let's call the second: Как вы можете видеть, имея вызов первой функции нам пришлось задейстовать конструктор перемещения, когда мы передали наш временный объект функции first(). Если бы у нас не было конструктора перемещения, то вызван бы был конструктор копирования. Во втором же случае, мы имеем ссылку и вызова конструктора копирования/перемещения не происходит, что в некотоых случаях может значительно ускорить код. На деле T&& используется для перегрузки функций(не только, но для начального понимания достаточно), чтобы различать, какая функция была вызвана с аргументом, являющимся временным объектом, а какая была вызвана с постоянным, lvalue-объектом. Для чего? Причина проста, если мы имеем временный объект, мы можем делать с ним всё, что угодно. С lavalue-объектом, как правило, нужно обращаться бережно. К примеру: #include class A { public: A() = default; void setName(const std::string& name) { std::cout << "Called setName with lvalue.\n"; m_Name = name; } void setName(std::string&& name) { std::cout << "Called setName with rvalue.\n"; m_Name = std::move(name); } private: std::string m_Name; }; int main() { A a; std::string name{"another name"}; a.setName("New name");//№1 a.setName(name);//№2 return 0; } Выведет: Called setName with rvalue. Called setName with lvalue. Как вы можете видеть, мы получили два разных вызова, которые зависит от типа выражения, переданного в качестве аргумента функции. И тогда, когда мы вызываем setName(std::string&& name) мы не вызываем конструктор копирования, чтобы поместить стркоу в m_Name, но мы вызываем конструктор перемещения, для этого. Который может быть многократно более эффективным, чем конструктор копирования(зависит от длины строки, в большей степени) Ответ 2
ixSci все верно описал, но все же можно объяснить и по-простому. Стандарт С++, начиная с С++11, ввел rvalue-ссылки (раньше были только обычные ссылки, T&, которые теперь называют lvalue-ссылки). Предварительно стоит сказать, что, говоря очень упрощенно - с точки зрения языка C++ любое выражение можно рассматривать как lvalue (у выражения можно взять адрес, например, Object obj; - можно взять адрес obj) , либо как rvalue (остальные случаи, например, любой литерал: 10; "mama";). И rvalue-ссылки, и lvalue-ссылки все еще остаются ссылками, а значит должны инициализироваться при создании. При этом rvalue-ссылки могут быть связаны только с rvalue (в отличие от lvalue-ссылок, которые могут быть связаны как с lvalue, так и с rvalue - когда ссылка объявлена как const T&). Таким образом, получаем: double a = 8.1; // переменная типа double double && b = 3.2; // rvalue-ссылка на временный объект double && c = 3*a-4; // rvalue-ссылка на временный объект double && d = func(c); // rvalue-ссылка на временный объект Вывод: В чем разница между 1 и 2 строкой? "a" и "b" - переменные разных типов, с несколько различным механизмом создания. Если бы вместо double использовался какой-нибудь пользовательский класс, то использование rvalue-ссылок позволило бы написать более оптимальный код, позволило бы использовать move-семантику и perfect-forwarding, но это уже предмет другого вопроса.
Комментариев нет:
Отправить комментарий