Мені відомо 5 загальних категорій, де перекомпіляція компілятора C ++ 03 як C ++ 11 може призвести до необмеженого підвищення продуктивності, що практично не пов'язане з якістю впровадження. Це всі варіанти семантики переміщення.
std::vector
перерозподілити
struct bar{
std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03
кожного разу, коли foo
буфер 's перерозподіляється в C ++ 03, він копіюється кожен vector
в bar
.
У C ++ 11 він замість цього переміщує bar::data
s, що в основному є вільним.
У цьому випадку це покладається на оптимізацію всередині std
контейнера vector
. У кожному з наведених нижче випадків використання std
контейнерів відбувається лише тому, що це об'єкти C ++, які мають ефективну move
семантику в C ++ 11 "автоматично" при оновленні компілятора. Об'єкти, які не блокують його, містять std
контейнер, також успадковують автоматичні вдосконалені move
конструктори.
Збій NRVO
Коли NRVO (оптимізація з назвою повернення) відмовляється, у C ++ 03 вона відновлюється при копіюванні, на C ++ 11 вона повертається назад у русі. Збої NRVO прості:
std::vector<int> foo(int count){
std::vector<int> v; // oops
if (count<=0) return std::vector<int>();
v.reserve(count);
for(int i=0;i<count;++i)
v.push_back(i);
return v;
}
або навіть:
std::vector<int> foo(bool which) {
std::vector<int> a, b;
// do work, filling a and b, using the other for calculations
if (which)
return a;
else
return b;
}
У нас є три значення - повернене значення та два різні значення в межах функції. Elision дозволяє "значення", що знаходяться у функції, "об'єднати" із поверненим значенням, але не один з одним. Вони обидва не можуть бути об'єднані з поверненою величиною без злиття один з одним.
Основне питання полягає в тому, що елісія NRVO неміцна, і код із змінами, що не знаходяться поблизу return
сайту, може раптом мати значне зниження продуктивності на цьому місці, без діагностики. У більшості випадків відмови NRVO C ++ 11 закінчується символом a move
, тоді як C ++ 03 закінчується копією.
Повернення аргументу функції
Елісіон також неможливий тут:
std::set<int> func(std::set<int> in){
return in;
}
у C ++ 11 це дешево: у C ++ 03 немає можливості уникнути копії. Аргументи до функцій не можуть бути повернені зі значенням повернення, оскільки час життя та розташування параметра та значення повернення керуються кодом виклику.
Однак C ++ 11 може переходити від одного до іншого. (У менш іграшковому прикладі щось може бути зроблено set
).
push_back
або insert
Нарешті вилучення в контейнери не відбувається: але C ++ 11 перевантажує rvalue переміщення операторів вставки, що зберігає копії.
struct whatever {
std::string data;
int count;
whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );
у C ++ 03 створюється тимчасовий whatever
, потім він копіюється у вектор v
. std::string
Виділено 2 буфери, кожен з однаковими даними, а один відкидається.
У C ++ 11 створюється тимчасовий whatever
. Потім whatever&&
push_back
перевантаження move
s, тимчасове, у вектор v
. Один std::string
буфер виділяється і переміщується у вектор. Порожнє std::string
відкидається.
Призначення
Викрадено з відповіді @ Jarod42 нижче.
Elision не може відбуватися із призначенням, але може перейти з банки.
std::set<int> some_function();
std::set<int> some_value;
// code
some_value = some_function();
тут some_function
повертається кандидат з ухилення від, але оскільки він не використовується для побудови об'єкта безпосередньо, він не може бути залишений. У C ++ 03 вищезазначене призводить до того, що вміст тимчасового копіюється в some_value
. У C ++ 11 він переміщується в some_value
, що в основному є безкоштовним.
Для повного ефекту від вищезазначеного вам потрібен компілятор, який синтезує конструктори переміщення та призначення для вас.
MSVC 2013 реалізує конструктори переміщення в std
контейнерах, але не синтезує конструктори переміщення для ваших типів.
Отже типи, що містять std::vector
s та подібні, не отримають таких покращень у MSVC2013, але почнуть отримувати їх у MSVC2015.
clang і gcc давно реалізували конструктори неявного руху. Компілятор Intel 2013 буде підтримувати неявне покоління конструкторів рухів, якщо ви переходите -Qoption,cpp,--gen_move_operations
(вони не роблять це за замовчуванням, намагаючись бути сумісними з MSVC2013).