Чи може використання "авто" C ++ 11 покращити продуктивність?


230

Я можу зрозуміти, чому autoтип C ++ 11 покращує правильність та ремонтопридатність. Я читав, що це також може покращити продуктивність ( Майже завжди Автоматичний Герб Саттер), але я пропускаю хороше пояснення.

  • Як можна autoпокращити продуктивність?
  • Хтось може навести приклад?

5
Перегляньте сторінку greeutter.com/2013/06/13/…, де йдеться про уникнення випадкових неявних перетворень, наприклад, від ґаджета до віджета. Це не звичайна проблема.
Jonathan Wakely

42
Чи приймаєте ви, «що робить меншою ймовірність ненавмисного песимізації» як поліпшення продуктивності?
5gon12eder

1
Виконання очищення коду лише в майбутньому, можливо
Кролл

Нам потрібна коротка відповідь: Ні, якщо ти хороший. Це може запобігти помилкам "noobish". C ++ має криву навчання, яка вбиває тих, хто все-таки не домагається цього.
Alec Teal

Відповіді:


309

autoможе сприяти ефективності, уникаючи беззвучних неявних перетворень . Приклад, який я вважаю переконливим, наступний.

std::map<Key, Val> m;
// ...

for (std::pair<Key, Val> const& item : m) {
    // do stuff
}

Бачите помилку? Ось ми, думаючи, що ми елегантно беремо кожен елемент на карті за допомогою посилання const та використовуємо новий діапазон для вираження, щоб зрозуміти наші наміри, але насправді ми копіюємо кожен елемент. Це тому, що std::map<Key, Val>::value_typeє std::pair<const Key, Val>, ні std::pair<Key, Val>. Таким чином, коли ми (неявно) маємо:

std::pair<Key, Val> const& item = *iter;

Замість того, щоб взяти посилання на існуючий об’єкт і не залишити його при цьому, ми повинні зробити перетворення типу. Вам дозволяється приймати посилання const на об'єкт (або тимчасовий) іншого типу до тих пір, поки наявна неявна конверсія, наприклад:

int const& i = 2.0; // perfectly OK

Перетворення типів - це дозволена неявна конверсія з тієї ж причини, яку ви можете перетворити в const Keya Key, але ми повинні побудувати тимчасовий нового типу, щоб дозволити це. Таким чином, ефективно наш цикл робить:

std::pair<Key, Val> __tmp = *iter;       // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it

(Зрозуміло, насправді __tmpоб’єкта насправді немає , він просто є для ілюстрації, насправді неназваний тимчасовий якраз і зобов'язаний itemпротягом життя).

Просто змінюється на:

for (auto const& item : m) {
    // do stuff
}

щойно врятували нам тонну копій - тепер тип посилань відповідає типу ініціалізатора, тому тимчасове або перетворення не потрібно, ми можемо просто зробити пряме посилання.


19
@Barry Чи можете ви пояснити, чому компілятор із задоволенням зробить копії, а не скаржиться на спроби трактувати std::pair<const Key, Val> const &як до std::pair<Key, Val> const &? Новачок у C ++ 11, не впевнений у тому, який діапазон є для autoцього.
Agop

@Barry Дякую за пояснення. Ось цей твір мені бракувало - чомусь я вважав, що ти не можеш мати постійне посилання на тимчасовий. Але, звичайно, можна - він просто припинить своє існування наприкінці своєї сфери.
Agop

@barry Я вас розумію, але проблема полягає в тому, що тоді немає відповіді, яка б охоплювала всі причини, щоб використовувати autoце підвищення продуктивності. Тож я напишу це своїми словами нижче.
Якк - Адам Невраумон

38
Я все ще не думаю, що це доказ того, що " autoпокращує продуктивність". Це лише приклад, який " autoдопомагає запобігти помилкам програміста, які руйнують продуктивність". Я стверджую, що між ними є тонка, але важлива відмінність. Все-таки +1.
Гонки легкості на орбіті

70

Оскільки autoвиводить тип ініціалізуючого виразу, перетворення типів не бере участь. У поєднанні з шаблоновими алгоритмами це означає, що ви можете отримати більш пряме обчислення, ніж якби ви самі складали тип, особливо коли ви маєте справу з виразами, тип яких ви не можете назвати!

Типовий приклад походить від (ab) з використанням std::function:

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1);  // bad
auto cmp2 = std::bind(f, _2, 10, _1);                       // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); };            // also good

std::stable_partition(begin(x), end(x), cmp?);

З cmp2і cmp3, весь алгоритм може вбудовувати виклик порівняння, тоді як якщо ви будуєте std::functionоб'єкт, виклик не тільки не може бути вбудованим, але й вам доведеться пройти поліморфний пошук у стираній внутрішній частині оболонки функції.

Ще один варіант цієї теми полягає в тому, що ви можете сказати:

auto && f = MakeAThing();

Це завжди посилання, пов'язане зі значенням виразу функції виклику функції і ніколи не будує додаткових об'єктів. Якщо ви не знали тип повернутого значення, ви можете змусити побудувати новий об'єкт (можливо, як тимчасовий) через щось подібне T && f = MakeAThing(). (Крім того, auto &&працює навіть тоді, коли тип повернення не є рухомим і повернене значення є первинним значенням.)


Отже, це "уникнення стирання типу" для використання auto. Ваш інший варіант - "уникайте випадкових копій", але потребує прикраси; Чому autoви даєте швидкість над просто введенням типу? (Я думаю, що відповідь "ти невірно визнаєш тип, і він мовчки перетворить") Що робить його менш чітко поясненим прикладом відповіді Баррі, ні? Тобто, є два основні випадки: автоматичне уникнення стирання типу та автоматичне уникнення помилок тихого типу, які випадково конвертуються, обидві мають витрату часу виконання.
Якк - Адам Невраумон

2
"не тільки виклик не може бути накреслений" - чому це тоді? Ви маєте в виду , що , в принципі , що - то заважає виклик бути devirtualized після потоку даних аналізу , якщо відповідні спеціалізації std::bind, std::functionі std::stable_partitionвсі були вбудовуваними? Або просто, що на практиці жоден компілятор C ++ не буде настільки агресивно впорядкований, щоб розібратися в безладді?
Стів Джессоп

@SteveJessop: Переважно останнє - після того, як ви std::functionпройдете конструктор, це буде дуже складно побачити через фактичний виклик, особливо з оптимізаціями з невеликими функціями (так що ви не хочете девіартуалізації). Звичайно, в принципі все так, ніби ...
Керрек СБ

41

Є дві категорії.

autoможна уникнути стирання типу. Існують типи, які неможливо змінити (наприклад, лямбда) та майже немоніальні типи (як результат std::bindабо інші речі, подібні до виразів).

Без цього autoвам доводиться набирати стирання даних до чогось подібного std::function. Стирання типу має витрати.

std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};

task1має тип стирання накладних даних - можливий розподіл купи, утруднення її складання та виклик віртуальних таблиць функцій. task2не має жодної. Лямбди потребують автоматичних або інших форм відрахування типу, щоб зберігати без стирання типу; інші типи можуть бути настільки складними, що вони потрібні лише на практиці.

По-друге, ви можете помилитися з типами. У деяких випадках неправильний тип працюватиме, здавалося б, ідеально, але спричинить копію.

Foo const& f = expression();

буде компілюватися, якщо expression()повертається Bar const&чи Barабо навіть Bar&, звідки Fooможна побудувати Bar. Буде створено тимчасове Foo, потім пов'язане f, і його термін експлуатації буде продовжений, поки fне закінчиться .

Програміст, можливо, мав на увазі Bar const& fі не мав намір робити там копію, але копія робиться незалежно.

Найпоширеніший приклад - тип *std::map<A,B>::const_iterator, якого std::pair<A const, B> const&немає std::pair<A,B> const&, але помилка - це категорія помилок, які мовчки коштують продуктивності. Ви можете побудувати a std::pair<A, B>з a std::pair<const A, B>. (Ключ на карті - const, тому що редагувати його - погана ідея)

І @Barry, і @KerrekSB вперше проілюстрували ці два принципи у своїх відповідях. Це просто спроба висвітлити два питання в одній відповіді з формулюванням, яке спрямоване на проблему, а не на прикладі.


9

Існуючі три відповіді дають приклади, коли використання autoдопомагає "робить меншою ймовірність ненавмисного песимізації", ефективно роблячи це "покращуючи продуктивність".

До монети є зворотний бік. Використання autoз об'єктами, у яких є оператори, які не повертають основний об'єкт, може призвести до неправильного (все-таки компільованого та зруйнованого) коду. Наприклад, у цьому питанні задається питання про те, як за autoдопомогою бібліотеки Eigen давали різні (неправильні) результати, тобто наступні рядки

const auto    resAuto    = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);

std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;

призвели до різного виходу. Звичайно, це пов'язано з ледачою оцінкою Ейгенса, але цей код є / повинен бути прозорим для користувача (бібліотеки).

Хоча тут продуктивність не сильно впливає, використання, autoщоб уникнути ненавмисної песимізації, може бути класифіковано як передчасна оптимізація або, принаймні, неправильна;).


1
Додано протилежне запитання: stackoverflow.com/questions/38415831/…
Леон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.