Семантика переміщення - це не обов'язково все настільки покращення, коли ви повертаєте значення - і коли / якщо ви використовуєте shared_ptr
(або щось подібне), ви, ймовірно, передчасно песимізуєте. Насправді майже всі досить сучасні компілятори роблять те, що називається оптимізацією повернутого значення (RVO) та оптимізацією іменованого повернення (NRVO). Це означає , що , коли ви повертаєте значення, а на самому ділі копіювання значення на все, вони просто передають прихований покажчик / посилання на те, де значення буде призначено після повернення, і функція використовує це для створення значення, де воно буде в кінцевому підсумку. Стандарт C ++ включає в себе спеціальні положення, які дозволяють це зробити, тому навіть якщо (наприклад) ваш конструктор копій має видимі побічні ефекти, не потрібно використовувати конструктор копій для повернення значення. Наприклад:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
Основна ідея тут досить проста: створити клас з достатньою кількістю вмісту, ми скоріше уникаємо його копіювання, якщо можливо ( std::vector
ми заповнюємо 32767 випадковими вставками). У нас є явний копіюючий копій, який показує нам, коли / якщо він буде скопійований. У нас також є трохи більше коду, щоб зробити щось із випадковими значеннями в об'єкті, тому оптимізатор не буде (принаймні легко) усунути все про клас тільки тому, що він нічого не робить.
Потім у нас є якийсь код для повернення одного з цих об'єктів з функції, а потім використовуємо підсумовування, щоб переконатися, що об’єкт справді створений, а не просто повністю ігнорований. Коли ми запускаємо його, принаймні з останніми / сучасними компіляторами, ми виявляємо, що конструктор копій, про який ми писали, ніколи не працює - і так, я впевнений, що навіть швидка копія з a shared_ptr
все ще повільніше, ніж копіювання не робиться зовсім.
Переміщення дозволяє зробити досить багато речей, які ви просто не могли (без них) зробити без них. Розгляньте частину "злиття" зовнішнього сортування злиття - у вас, скажімо, 8 файлів, які ви збираєтеся об'єднати разом. В ідеалі ви хотіли б помістити всі 8 цих файлів у vector
- але оскільки vector
(на C ++ 03) потрібно вміти копіювати елементи, а ifstream
s неможливо скопіювати, ви застрягли з деякими unique_ptr
/ shared_ptr
, або щось із цього наказу, щоб мати змогу поставити їх у вектор. Зауважте, що навіть якщо (наприклад) ми reserve
помістимо простір, vector
щоб ми впевнені, ifstream
що їх ніколи насправді не буде скопійовано, компілятор цього не знатиме, тому код не буде компілюватися, хоча ми знаємо, що конструктор копій ніколи не буде застосовується все одно.
Незважаючи на те, що його все ще неможливо скопіювати, у C ++ 11 ifstream
можна перемістити. У цьому випадку об'єкти, ймовірно, ніколи не будуть переміщені, але той факт, що вони можуть бути при необхідності, підтримує компілятор щасливим, тому ми можемо розміщувати наші ifstream
об’єкти vector
безпосередньо, без жодних злому розумних покажчиків.
Вектор, який розширюється, є досить пристойним прикладом часу, який семантика переміщення дійсно може бути / корисний, хоча. У цьому випадку RVO / NRVO не допоможе, оскільки ми не маємо справу з поверненим значенням функції (або чим-небудь дуже схожим). У нас є один вектор, який містить деякі об'єкти, і ми хочемо перемістити ці об'єкти в новий, більший шматок пам'яті.
У C ++ 03 це було зроблено шляхом створення копій об'єктів у новій пам'яті, а потім знищення старих об'єктів у старій пам'яті. Однак зробити ці копії просто для того, щоб викинути старі, було марною тратою часу. У C ++ 11 ви можете очікувати їх переміщення. Зазвичай це дозволяє нам, по суті, робити дрібну копію замість (як правило, набагато повільнішої) глибокої копії. Іншими словами, за допомогою рядка або вектора (лише для кількох прикладів) ми просто копіюємо вказівник (и) в об'єкти, замість того, щоб робити копії всіх даних, на які посилаються вказівники.
shared_ptr
просто заради швидкого копіювання), і якщо семантика переміщення може досягти того ж, майже без кодування, семантики та чистоти-покарання.