Герб Саттер про використання auto в С++

  • 1 октября, 16:08
  • 3708
  • 0

*цей текст є перекладом уривку з виступу Герба Саттера, присвяченого використанню auto.Герб Саттер, эксперт по языку C++Герб Саттер, експерт по мові C++

Противники використання auto часто кажуть, що «код стає менш читабельним, оскільки не ясно, якого саме типу змінна. Доводиться витрачати час на пошук оголошення функції або розбиратись з тим, який кінцевий тип буде у виразу». Частково це правда, особливо у випадку виразів auto x = expr;. Також правда, що будь-яку функціональність можна використовувати надмірно. Однак, в цілому цей аргумент не здається мені важливим, оскільки я маю проти нього два не дуже важливих заперечення, і ще два дуже серйозних.

Не такі важливі контраргументи виглядають так:

  1. При використанні auto все ще можна прямо задавати тип виразу.
  2. Аргумент не має значення, якщо ви працюєте в IDE — вона підкаже вам тип змінної, лише поворухніть мишкою. Втім, він все ще існує, коли мова йде про код поза IDE.

Але важливішими є два інші контраргументи:

  1. Такий підхід зміщується до програмування реалізації, а не інтерфейсу (implementations, not interfaces). Надмірна любов до прямо визначених типів робить код менш загальним та більш залежним, а тому крихким та обмеженим.
  2. Насправді ми (тобто, ви) ігноруємо типи весь час...

Думаєте, що це не так? Розглянемо такий код:

template<class Container, class Value>
void append_unique( Container& c, const Value& v ) {
    if( find(begin(c), end(c), v) == end(c) )
        c.emplace_back(v); 
    assert( !c.empty() );
}

Швидке питання: як ви гадаєте, скільки типів згадується у цій функції? Спробуйте дати найбільшу можливу версію.

Подумайте...

... і можна назвати правильну відповідь: нуль, нічогісінько. Якщо прискіпуватись, то один тип все ж є: це void. Але це ж те ж саме що «без результату», тобто його тут і немає фактично.

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

Отже, спробуємо перелічити всі випадки, коли ми ігноруємо типи. 

Перший з них — параметри функції:

  • Якого типу Container? Ми не знаємо, і це чудово... підійде все, у чого є методи begin, end, emplace_back та empty. Справді чудово, бо такий підхід відповідає принципу відкритості/закритості та дозволяє розширювати функціональність — навіть через багато років ми зможемо додати новий тип і append_unique буде з ним коректно працювати. Цікаво, що навіть пропозиція концепцій для шаблонів, яка зараз перебуває на розгляді у комітеті стандартизації, не змінить роботу нашої функції.

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

  • Якого типу Value? Знову ж таки, ми не знаємо і не хочемо знати... підійде будь-що, що можна передати у find та embrace_back. Можливо, зараз хтось скаже: «Та ні, ми знаємо тип, це тип значень контейнеру!» Але ні, це зовсім не обов'язково. Важливо, щоб ці типи можна було привести один до одного. Наприклад, може бути код vector<string> vec; append_unique(vec, “xyzzy”); і в ньому “xyzzy” буде типу const char[6] а не string.

Друге, значення, які повертає функція:

  • Що є результатом find? Якийсь результат, так само як і у begin(c). Але ми не знаємо, що саме є таким результатом, і це не важливо. Якщо буде цікаво, можна глянути у реалізації, але має значення лише те, що цей тип можна порівняти з end(c).
  • Що повертає empty()? Та ми навіть не задумуємось. Щось придатне для порівняння, на зразок bool... не суть, головне, щоб для нього можна було взяти протилежне значення за допомогою !.

Третє, параметри функцій:

  • Який тип отримує emplace_back? не знаємо. Може, такий, як у v, може й ні. Усе одно. Можемо передати туди v? Звісно можемо.

І це все лише в одному прикладі. Є багато інших ситуацій, коли ми ігноруємо типи:

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

П'яте, будь-яке використання базового класу. Там суть саме в тому, що ми не знаємо, який саме динамічний клас використовується.

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

Сьоме, будь-яке використання function<>, bind або подібного. Просто задумайтесь, наскільки мало ми про це знаємо і наскільки це робить нас щасливими. Візьмемо для прикладу вираз function<int(string)>. Ми не тільки не знаємо, якого об'єкту чи функції він стосується, ми навіть уявити не можемо, як саме виглядатиме реалізація, бо приведення типів може відбуватись в усі сторони. Дозволяється повертати будь-що, що приводиться до цілого числа; і приймати будь-який аргумент, який буде конвертуватися в рядок. Більш-менш ясно, що це «щось» приймає рядки й видає числа. Незнання — сила.

Восьме, будь-яка узагальнена лямбда (C++14 generic lambda function).

Можливо, вдасться навести більше прикладів.

Таким чином, втрата вірності може бути шкідливою в інших сферах життя. Але у програмуванні краще зраджувати звичці використовувати один і той самий тип, бо це дозволяє створювати код, придатний для повторного використання.

Джерело: codeguida.com


0 комментариев
Сортировка:
Добавить комментарий