Скільки коштує занадто багато ключових слів C ++ 11?


218

Я використовую нове autoключове слово, доступне в стандарті C ++ 11, для складних шаблонових типів, для чого я вважаю, що він був розроблений. Але я також використовую його для таких речей, як:

auto foo = std::make_shared<Foo>();

І більш скептично:

auto foo = bla(); // where bla() return a shared_ptr<Foo>

Я не бачив багато дискусій на цю тему. Здається, що це autoможе бути надмірно використане, оскільки тип часто є формою документації та перевірки правильності. Де ви проводите лінію у використанні autoта які рекомендовані випадки використання для цієї нової функції?

Для уточнення: я не прошу філософської думки; Я прошу про призначення цього ключового слова стандартним комітетом, можливо, з коментарями щодо того, як це цільове використання реалізується на практиці.

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


Це, хоча, це питання з питань питань, а не обговорення. Ви задали дуже загальне запитання, і я сумніваюся, що хтось зможе дати вам що-небудь, крім високо суб'єктивного. (тому -1)
TravisG

15
@heishe, я додав уточнення. Якщо ви читаєте це питання в узагальненому вигляді , це , здається, задає суб'єктивна думка, але на самому справі , якщо ви використовували autoключове слово, то ви знаєте , як це передбачається використовувати. Ось що я запитую, як хтось новачок у цій функції, як я повинен використовувати її?
Алан Тьюрінг

13
Цю дискусію я бачив усюди, коли C # запроваджувався var(тобто колись люди переймалися думкою, що це не динамічний набір тексту зрештою). Якщо ви хочете, можете почати з цього питання і переглядати відповідні питання.
Р. Мартіньо Фернандес

2
@Lex: або щось законне, або це не так; називати щось "поганим", що є законним, є суб'єктивним за визначенням. Тобто, називати auto foo = bla();«поганим» - це явно думка, а не факт, що обумовлює це питання та відповідає на дискусію, що робить його актуальним для Програмістів SE, саме на що вказують близькі голоси. /
знизати

3
Перегляд Herb Sutter з цього приводу: biljeutter.com/2013/06/13/…
rafak

Відповіді:


131

Я думаю, що слід використовувати autoключове слово, коли важко сказати, як написати тип з першого погляду, але тип правої частини виразу очевидний. Наприклад, використовуючи:

my_multi_type::nth_index<2>::type::key_type::composite_key_type::
    key_extractor_tuple::tail_type::head_type::result_type

щоб отримати тип складеного ключа boost::multi_index, навіть якщо ви знаєте, що це int. Ви не можете просто писати, intтому що це може бути змінено в майбутньому. Я б писав autoу цьому випадку.

Тож якщо autoключове слово покращує читабельність у конкретному випадку, тоді використовуйте його. Ви можете писати, autoколи читачеві очевидно, який тип autoявляє собою.

Ось кілька прикладів:

auto foo = std::make_shared<Foo>();   // obvious
auto foo = bla();                     // unclear. don't know which type `foo` has

const size_t max_size = 100;
for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors
                                      // since max_size is unsigned

std::vector<some_class> v;
for ( auto it = v.begin(); it != v.end(); ++it )
                                      // ok, since I know that `it` has an iterator type
                                      // (don't really care which one in this context)

18
Це не здається дуже вдалою рекомендацією. Як часто ви не знаєте тип об'єкта? (Поза шаблонами, тобто.) І якщо ви не знаєте, що вони вводять, подивіться, не лінуйтеся і користуйтеся авто.
Пол Манта

9
@Paul: часто ти лише знаєш або потребуєш знати найважливішу частину типу, наприклад, що це ітератор, але ти не знаєш і не байдуже, чи це ітератор векторів чи ітератор пов'язаних список; у цих випадках ви дійсно не хочете витрачати час та простір на екрані, намагаючись з’ясувати, як записати типи.
Лі Лі Раян

45
Незалежно від того, чи сподобається це C ++, чи ні, C ++ 0x приверне людей, які ніколи б не використовували C ++. І ті користуватимуться авто всюди.
Проф. Фолкен

6
@ R.MartinhoFernandes - ні, єдине, що зрозуміло, - це те, що bla() повертаєш, ти йому це даєш foo.
Луїс Мачука

8
@LuisMachuca моя відповідь була мовою, що говорила про те, що нечесно наводити в підручнику приклад неправильного іменування змінних та функцій та звинувачувати його відсутність читабельності у виведенні типу.
Р. Мартіньо Фернандес

62

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


12
Я б особливо сказав: «const auto &»
Віктор Шер

2
Краще використовувати auto&&в складних ситуаціях edmundv.home.xs4all.nl/blog/2014/01/28/…
KindDragon

2
@KindDragon: Це хороше правило, але я вважаю за краще використовувати const auto&або, const autoякщо я прямо не хочу мутувати або рухатись.
Джон Перді

Msgstr " Використовуйте автоматичну функцію скрізь, де можна ". Це означає, що я повинен писати auto str = std::string();замість std::string str;?
Кальмарій

4
Використання автоматичного в усьому місці зробить ваш код менш читабельним і важче налагодити, оскільки вам доведеться виводити типи самостійно під час читання коду.
SubMachine

52

Легко. Використовуйте його, коли вам все одно, що це за тип. Наприклад

for (const auto & i : some_container) {
   ...

Мене тут все хвилює - це те i, що є в контейнері.

Це трохи схоже на typedefs.

typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;

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

Або розглянути

for (auto i = some_container .begin (); ...

Тут все, що мені важливо, це те, що це підходящий ітератор, підтримуючий operator++(), це на зразок набирання качки в цьому відношенні.

Також тип лямбда не може бути написаний, так auto f = []...це хороший стиль. Альтернативою є кастинг, std::functionале це поставляється з накладними витратами.

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

Якщо ви можете прибрати деяку надмірність у своєму коді, не вводячи побічних ефектів, тоді це повинно бути добре.

Контрприклади (запозичені з чужих відповідей):

auto i = SomeClass();
for (auto x = make_unsigned (y); ...)

Тут ми НЕ дбаємо, що це за тип, тому ми повинні писати Someclass i;іfor(unsigned x = y;...


1
Гм. Не так просто. Вона компілюється і запускається, і ви щойно вистрілили собі в ногу, якщо предмети є нетривіальними об'єктами - ваша ітерація викликає конструктор копій та деструктор на кожному кроці ітерації. Якщо ви збираєтеся сліпо використовувати авто в ітераторі на основі діапазону, ймовірно, це має бути "for (const auto & item: some_container)" замість "for (auto item: some_container)".
Дон Хетч

2
Не завжди, але добре, напевно, ви хочете довідок. І що? Це ні до чого auto.
шприц

Я дійсно не розумію твій останній коментар. Я спробував пояснити, чому ваша стратегія не виглядає дуже вдалою для мене, і чому я її сприймаю.
Дон Хетч

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

43

Действуй. Використовуйте autoбудь-де це полегшує написання коду.

Кожна нова функція будь-якою мовою буде надмірно використана хоча б деякими типами програмістів. Тільки завдяки помірному використанню деякими досвідченими програмістами (а не ноубами) решта досвідчених програмістів вивчають межі правильного використання. Екстремальне надмірне використання, як правило, є поганим, але може бути хорошим, оскільки таке надмірне використання може призвести до поліпшення функції або кращої можливості її заміни.

Але якби я працював над кодом з більш ніж декількома подібними рядками

auto foo = bla();

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


38

На C ++ та за її межами 2012 року на панелі « Запитайте у нас все» відбувся фантастичний обмін між Андрієм Олександреску, Скоттом Мейєрсом та Герб Саттером, який говорив про те, коли користуватися, а не використовуватиauto . Перехід до хвилини 25:03 для 4-хвилинної дискусії. Всі три динаміки дають чудові бали, про які слід пам’ятати, коли їх не використовувати auto.

Я настійно закликаю людей прийти до власного висновку, але моє відвезення було використовувати autoскрізь, якщо :

  1. Це шкодить читабельності
  2. Існує занепокоєння щодо автоматичного перетворення типу (наприклад, від конструкторів, призначення, проміжних типів шаблонів, неявного перетворення між цілими ширинами)

Ліберальне використання засобів explicitсприяє зменшенню стурбованості останніх, що допомагає мінімізувати кількість часу, що викликає перше питання.

Перефразовуючи те, що Герб сказав, "якщо ви не робите X, Y і Z, використовуйте auto. Дізнайтеся, що таке X, Y і Z, і продовжуйте користуватися autoскрізь."


4
Напевно, також варто посилатися на "майже завжди авто" - biljeutter.com/2013/08/12/…
Роб Старлінг

37

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

   for(auto i = container.begin(); i != container.end(); ++i);

auto тут не шкодить читабельності.

Інший приклад - тип правила парсера, який може бути довгим і складним. Порівняйте:

   auto spaces = space & space & space;

з

r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&> spaces = 
   space & space & space;

З іншого боку, коли тип відомий і простий, це набагато краще, якщо він буде чітко зазначено:

int i = foo();

а не

auto i = foo();

2
Звичайно, наявність мовного циклу на основі діапазону робить ваш перший приклад менш захоплюючим. 8v)
Фред Ларсон

@Fred: тип все ще може бути громіздким (я думаю, що асоціативні контейнери)
Matthieu M.

3
@Fred: Кожен раз , коли ваші кордони не begin()та end(), або ваш розмір кроку нічого, крім одного, або ви змінюєте контейнер , як ви петлі, діапазон на основі для заяви не допоможе.
Dennis Zickefoose

6
@geotavros: А r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&>чи?
Dennis Zickefoose

7
@geotavros: Або ви можете побачити, що таке тип space, і шукати його. Це тим більше корисна інформація ... зрештою, питання полягає не в тому, "який тип це нова змінна", а в тому, "що space & space & spaceозначає?" Фактичний тип виразу - це лише шум.
Dennis Zickefoose

19

auto може бути дуже небезпечним у поєднанні з шаблонами виразів, які багато використовуються лінійними бібліотеками алгебри, такими як Eigen або OpenCV.

auto A = Matrix(...);
auto B = Matrix(...);
auto C = A * B; // C is not a matrix. It is a matrix EXPRESSION.
cout << C; // The expression is evaluated and gives the expected result.
... // <code modifying A or B>
cout << C; // The expression is evaluated AGAIN and gives a DIFFERENT result.

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

auto C = Matrix(A * B); // The expression is now evaluated immediately.

1
Це здається дивним поведінкою для початку. Якщо я перемножую дві матриці, навіть якщо операція лінива, я не очікував би її повторної оцінки, я б очікував, що вона збереже оцінений стан після початкової оцінки. Якщо ви хочете змінити параметри, не змінюючи початкові параметри, чи не все-таки доведеться відновити вираз? Або він розроблений для подібної ситуації потокової обробки, де вихідні параметри постійно змінюються, але процедура залишається тією ж?
JAB

1
Незалежно від того, A*Bвираз скопійований у autoзмінну чи щось інше, поведінка, яку ви описуєте, все ще присутня.
xtofl

Помилка не викликана використанням auto.
Джим Балтер

@JAB: Він призначений для підтримки спрощень та синергії між операціями, наприклад diag(A * B), не потрібно витрачати цикли, обчислюючи недіагональні елементи.
Бен Войгт

Я також помітив код типу "for (auto o: vecContainer)", де "o" був важким об'єктом, який щоразу копіюється. Моя рекомендація полягає в тому, щоб використовувати його для того, що спочатку було призначено, тобто шаблонів (зі складними правилами дедукції) та трудомістких typedef. Навіть тоді ви повинні бути обережними та розрізняти авто та авто &.
gast128

10

Я використовую autoобмеження wihout і не зіткнувся з жодною проблемою. Я навіть іноді в кінцевому підсумку використовую його для таких простих типів int. Це робить c ++ мовою вищого рівня для мене і дозволяє оголошувати змінну в c ++, як у python. Після написання пітонного коду я навіть іноді пишу, наприклад

auto i = MyClass();

замість

MyClass i;

Це один випадок, коли я б сказав, що це зловживання autoключовим словом.

Часто я не заперечую, який саме тип об’єкта, мене більше цікавить його функціональність, і як назви функцій взагалі говорять щось про об’єкти, які вони повертають, autoне шкодить: наприклад auto s = mycollection.size(), я можу здогадатися, що sце буде якесь ціле число, і в рідкісному випадку, коли я дбаю про точний тип, давайте перевіримо прототип функції тоді (я маю на увазі, я вважаю за краще перевірити, коли мені потрібна інформація, а не апріорі, коли пишеться код, просто на випадок, коли б це було корисно колись, як в int_type s = mycollection.size()).

Щодо цього прикладу з прийнятої відповіді:

for ( auto x = max_size; x > 0; --x )

У своєму коді я все-таки використовую autoв цьому випадку, і якщо я хочу xбути без підпису, то я використовую функцію утиліти з назвою сказати make_unsigned, яка чітко висловлює мою стурбованість:

for ( auto x = make_unsigned(max_size); x > 0; --x )

відмова від відповідальності: я просто описую своє використання, я не компетентний давати поради!


1
@ChristianRau: не був саркастичним. Зауважте, що я не рекомендував використання auto i = MyClass().
rafak

3
Наступний: см: herbsutter.com/2013/06/13 / ... . Використовувати, наприклад as_unsigned, рекомендується там, або навіть auto w = widget{};.
rafak

3

Однією з найважливіших проблем програми C ++ є те, що вона дозволяє використовувати неініціалізовану змінну . Це призводить нас до неприємної поведінки програми. Слід зазначити, що сучасний компілятор зараз кидає відповідні / попереджувальні повідомлення, якщо програма стомлює їх використовувати.

Просто для ілюстрації цього розглянемо нижче програму c ++:

int main() {
    int x;
    int y = 0;
    y += x;
}

Якщо я компілюю цю програму за допомогою сучасного компілятора (GCC), вона дає попередження. Таке попередження може бути не дуже очевидним, якщо ми працюємо з реальним складним виробничим кодом.

main.cpp: У функції 'int main ()':

main.cpp: 4: 8: попередження : 'x' використовується неініціалізованим у цій функції [-Вінваліталізований]

y + = x;

    ^

===================================================== ================================ Тепер, якщо ми змінимо нашу програму, яка використовує автоматичне , то компілюючи ми отримуємо наступне:

int main() {
    auto x;
    auto y = 0;
    y += x;
}

main.cpp: У функції 'int main ()':

main.cpp: 2: 10: error : оголошення "auto x" не має ініціалізатора

 auto x;

      ^

При автоматичному використанні неініціалізованої змінної неможливо. Це головна перевага, яку ми можемо отримати (безкоштовно), якщо почнемо використовувати авто .

Цю концепцію та інші великі сучасні концепції C ++ пояснив експерт C ++ Herb Shutter у своїй розмові про CppCon14 :

Назад до основ! Основи сучасного стилю C ++


2
Так. І щоб вказати тип, ви можете ініціалізувати як 0i, 0u, 0l, 0ul, 0.0f, 0.0, або навіть int (), без знака (), double () тощо.
Робінсон

6
Цей аргумент смішний. Ви говорите: "Ви не можете забути ініціалізатор, оскільки ви отримаєте помилку компілятора", але для цього потрібно пам'ятати auto.
Гонки легкості на орбіті

1
@LightnessRacesinOrbit: Я зрозумів (навчився) цю концепцію з розмови про Герб Саттер. Мені здалося, що це логічна / практична порада з розмов про трави, тому думав поділитися із спільнотою
Мантош Кумар

2
Я не думаю, що це гарна мотивація користуватися авто. Просто увімкніть попереджувальний прапор вашого компілятора для використання неініціалізованих змінних та врахуйте всі попередження. Це не означає, що ви не повинні використовувати auto- але вам це не потрібно, щоб відмовитися від цього питання.
einpoklum

2

Одна з загроз, яку я зазначив, стосується посилань. напр

MyBigObject& ref_to_big_object= big_object;
auto another_ref = ref_to_big_object; // ?

Проблема в іншому_ref насправді не є посиланням, в цьому випадку це MyBigObject замість MyBigObject &. Ви копіюєте великий об'єкт, не усвідомлюючи цього.

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

auto another_ref = function_returning_ref_to_big_object();

вам знадобиться "auto &" або "const auto &"

MyBigObject& ref_to_big_object= big_object;
auto& another_ref = ref_to_big_object;
const auto& yet_another_ref = function_returning_ref_to_big_object();

1

Використовуйте autoтам, де є сенс для виводу типу. Якщо у вас є щось, для якого ви знаєте, що це ціле число, або ви знаєте, що це рядок, просто використовуйте int / std :: string тощо. Я б не турбувався про "надмірне використання" мовної функції, якщо це не стане смішним, або обтяжує код.

Це все-таки моя думка.


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

Це правда, хоча я думаю, що досить просто сказати, коли це неоднозначно. Якщо сумніваєтесь, використовуйте коментар!
LainIwakura

1
Чи не було б сенсу використовувати тип?
Михайло

Деякі розробники скажуть, що тип завжди слід робити із висновку!
Арафангіон

Використовуйте коментар ...... або просто використовуйте тип і коментар не потрібен?
user997112

1

autoключове слово можна використовувати лише для локальної змінної, а не для аргументів або членів класу / структури. Отже, користуватися ними можна де завгодно безпечно і життєздатно. Я їх дуже багато використовую. Тип виводиться під час компіляції, налагоджувач показує тип під час налагодження, sizeofповідомляє про це правильно, decltypeдав би правильний тип - шкоди немає. Я ніколи не вважаю autoсебе занадто зловживаним!


0

TL; DR: Див. Правило внизу.

Загальноприйнятий відповідь пропонує наступне правило:

Використовуйте, autoколи важко сказати, як написати тип з першого погляду, але тип правої частини виразу очевидний.

Але я б сказав, що це занадто обмежувально. Іноді я не дбаю про типи, оскільки вислів є досить інформативним, не можу зайняти час, щоб визначити тип. Що я маю на увазі під цим? Розглянемо приклад, який з’явився в деяких відповідях:

auto x = f();

Що робить це прикладом неправильного використання auto? Це моє незнання f()типу повернення? Що ж, це справді може допомогти, якби я це знав, але - це не моя головна турбота. Набагато серйознішою проблемою є те , що xі f()досить безглуздо. Якби у нас були:

auto nugget = mine_gold();

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

Тому моя відповідь: Використовуйте, autoколи компілятор дозволяє, якщо:

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

І також:

  • Перед заміною autoконкретним типом віддайте перевагу давати змістовне ім’я (яке не містить імені типу) .

-3

Один з моїх гірких переживань auto- це використання його з лямбда-виразами :

auto i = []() { return 0; };
cout<<"i = "<<i<<endl; // output: 1 !!!

Насправді, тут iвирішено функціонувати покажчик int(*)(). Це просто cout, але уявіть, які помилки компіляції / помилок виконання можуть спричинити при використанні template.

Вам слід уникати auto таких виразів і вводити належний returnтип (або керований decltype())

Правильне використання для наведеного вище прикладу було б,

auto i = []() { return 0; }(); // and now i contains the result of calling the lambda  

7
Ви зробили жахливу роботу, пояснивши, що це стосується авто. Ви створили функцію, а потім роздрукували її ... Гаразд?
Dennis Zickefoose

5
@iammilind: а що це стосується авто?
Р. Мартіньо Фернандес

7
Я вважаю малоймовірним, що хтось хотів би поставити це ()в кінці кінця. Лямбди існують як функції, і саме звідси відбувається перетворення покажчика функції. Якщо ви хочете відразу зателефонувати, навіщо взагалі використовувати лямбда? auto i = 0;працює досить добре.
Р. Мартіньо Фернандес

4
Чи можете ви хоча б описати сценарій, де auto x = []() { /* .... whatever goes in here ... */ }()краще, ніж auto x = /* .... whatever goes in here ... */;, тобто, те саме, без лямбда? Я вважаю це досить безглуздим, з тієї ж причини auto x = 42 + y - 42безглуздо.
Р. Мартіньо Фернандес

6
-1 це не помилка авто. Тип лямбда не може бути написаний, тому потрібне автоматичне , якщо ви забудете викликати функцію, то це ваш власний огляд! Ви можете так само випадково помістити незваний покажчик функції в C printf.
спрф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.