Чому мені потрібно явно писати ключове слово "auto"?


80

Я переходжу до C ++ 11 з C ++ 98 і ознайомився з autoключовим словом. Мені було цікаво, чому нам потрібно явно оголосити, autoчи компілятор може автоматично вивести тип. Я знаю, що C ++ - це сильно набрана мова, і це правило, але чи не вдалося досягти того самого результату без явного оголошення змінної auto?


Пам’ятайте, що сім’я С чутлива до регістру. Перейдіть до налагодження коду JS, де автор пропустив "var" і використовуйте окрему змінну з іменами, як "Bob", "bob" і "boB". Тьфу.
PTwr

46
Навіть якби це було можливо, це була б в міру жахлива ідея. Можливо, найбільшою слабкістю Python (та подібних мов) є відсутність синтаксису оголошення. Основним джерелом помилок є те, що просте призначення призводить до створення нової змінної.
Конрад Рудольф

@KonradRudolph: Однак JS не кращий із синтаксисом оголошення. Я думаю, що вони мали на увазі сказати, що це неможливість чітко обмежити область дії змінної.
user541686

4
@Mehrdad “змінює семантику” ≠ “обов’язково”. Проблема в тому, що JavaScript дійсно приймає неявні декларації. Так, вони семантично відрізняються, але це не допомагає ні найменше.
Конрад Рудольф

1
Дивіться також подвійне запитання "чому справжні користувачі Perl використовують" моє "ключове слово stackoverflow.com/questions/8023959/why-use-strict-and-warnings/…
Yann TM

Відповіді:


156

Якщо autoвідмовитись від явного, це призведе до порушення мови:

напр

int main()
{
    int n;
    {
        auto n = 0; // this shadows the outer n.
    }
}

де ви можете побачити, що випадання autoне призведе до затінення зовнішнього n.


8
Набирав точно те саме. Відмежування призначення від ініціалізації вимагало б довільного вибору з боку стандарту. Оскільки ми вже маємо правило, що "все, що може бути декларацією, є декларацією", ми наступаємо на дуже каламутну воду.
StoryTeller - неграндерка Моніка

4
Це не проблема. Як і golang, ви, очевидно, можете використовувати щось на зразок n := 0введення нової змінної. Чому autoвикористовується - це питання, засноване на думці.
llllllllll

23
@liliscent - це засновано на думках? (а) Це вже було зарезервоване ключове слово. (б) Значення цілком зрозуміле. (c) Це уникає необхідності введення нових лексем (наприклад :=). (d) Це вже вписується в граматику. Думаю, тут дуже мало місця для думок.
StoryTeller - неграндерка Моніка

2
@StoryTeller Вам не потрібні нові маркери, якщо x = f()оголошено нову змінну (якщо вона ще не існує), отримуючи тип поверненого значення f ... Однак, вимагаючи від автоматичного явного оголошення змінної, зменшується ризик оголошення нових змінних випадково (наприклад, через помилку ...).
Аконкагуа,

34
@Aconcagua - "оголошує нову змінну (якщо ще не існує)" Але тінізація є частиною мови, і все одно повинна працювати, як ілюструє Вірсавія. Це більша проблема, ніж можна собі уявити. Йдеться не про мовний дизайн з нуля, а про зміну живої мови дихання. Зробити набагато складніше. Як би змінити кермо на швидкісному автомобілі.
StoryTeller - неграндерка Моніка

40

Ваше запитання дозволяє два тлумачення:

  • Навіщо нам взагалі потрібен "авто"? Чи не можемо ми просто кинути це?
  • Чому ми зобов'язані використовувати авто? Хіба ми не можемо просто мати це неявно, якщо воно не дано?

Вірсавія відповіла приємно на перше тлумачення, на друге розглянемо наступне (припускаючи, що поки що не існує інших заяв; гіпотетично дійсний С ++):

int f();
double g();

n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double

if(n == d)
{
    n = 7; // reassigns n
    auto d = 2.0; // new d, shadowing the outer one
}

Це було б можливо, з іншими мовами цілком вдається (ну, окрім тініруючого питання) ... Однак у C ++ це не так, і питання (у сенсі другого тлумачення) зараз: Чому ?

Цього разу відповідь не така очевидна, як у першій інтерпретації. Одне очевидно, однак: явна вимога до ключового слова робить мову безпечнішою (я не знаю, чи саме це спонукало мовний комітет до свого рішення, але це все ще залишається суттю):

grummel = f();

// ...

if(true)
{
    brummel = f();
  //^ uh, oh, a typo...
}

Чи можемо ми домовитись про це, не потребуючи подальших пояснень?

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

(цитований коментар psmears через його важливість - спасибі за натяк)


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

1
@psmears Мови, такі як Python, уникають цього, вимагаючи явної специфікації змінної як глобальної / нелокальної для призначень; за замовчуванням він просто створює нову локальну змінну з таким іменем. (Звичайно, ви можете читати з глобальної змінної, не вимагаючи global <variable>оператора.) Це, звичайно, потребує ще більшої модифікації мови C ++, тому, ймовірно, це не буде здійсненним.
JAB

@JAB - так, я це знаю ... Я не згадував про це, тому що, як ти кажеш, це вимагало б ще більшої модифікації мови :)
psmears

FWIW, що рухало мовним комітетом, це, швидше за все, історія. AFAIK, коли C був спочатку написаний, локальні змінні зберігались у стеку, і C вимагав, щоб усі змінні були явно оголошені першими в блоці. Це дозволило компілятору визначити вимогу до зберігання для цього блоку перед компіляцією решти коду і дозволило йому видати правильну послідовність команд для розподілу місця в стеку. IIRC MOV R6 R5 SUB #nnn R6на PDP-11 припускаючи, що R5 використовується як вказівник кадру, а R6 - вказівник стека. nnn - кількість байтів необхідного сховища.
dgnuff

2
Людям вдається використовувати Python, який із задоволенням оголошує змінну кожного разу, коли в лівій частині завдання з’являється нове ім’я (навіть якщо це ім’я друкарська помилка). Але я вважаю це однією з найсерйозніших вад мови.
хоббс

15

чи не вдалося досягти того самого результату без явного оголошення змінної auto?

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

Чи не вдалося досягти того самого результату без явного використання заповнювача типу ?

Хіба це не було можливо ? Звичайно, це було "можливо". Питання в тому, чи варто було б докладати зусиль для цього.

Більшість синтаксисів інших мов, що не вводять імена, працюють одним із двох способів. Існує спосіб Go-like, де name := value;оголошується змінна. І є спосіб, подібний до Python, де name = value;оголошує нову змінну, якщо nameвона раніше не була оголошена.

Припустимо, що жодних синтаксичних проблем із застосуванням того чи іншого синтаксису до C ++ не існує (хоча я вже бачу, що identifierпісля цього :в C ++ означає «зробити мітку»). Отже, що ви втрачаєте порівняно із заповнювачами?

Ну, я більше не можу цього робити:

auto &name = get<0>(some_tuple);

Дивіться, autoзавжди означає "цінність". Якщо ви хочете отримати посилання, вам потрібно явно використовувати &. І він по праву не зможе скомпілювати, якщо вираз призначення є першим значенням. Жоден із синтаксисів на основі присвоєння не має можливості розрізнити посилання та значення.

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

auto name = get<0>(some_tuple);

Це копіює з кортежу, створюючи об’єкт, незалежний від some_tuple. Іноді це саме те, що ви хочете. Це ще корисніше, якщо ви хочете перейти від кортежу з auto name = get<0>(std::move(some_tuple));.

Добре, можливо, ми могли б трохи розширити ці синтаксиси, щоб врахувати цю відмінність. Можливо, &name := value;або &name = value;означало б вивести посилання типу auto&.

Добре, гаразд. Як що до цього:

decltype(auto) name = some_thing();

О, це правильно; С ++ насправді має два заповнювачі: autoіdecltype(auto) . Основна ідея цього відрахування полягає в тому, що він працює точно так, як якщо б ви робили це decltype(expr) name = expr;. Отже, у нашому випадку, якщо some_thing()це об’єкт, він виведе об’єкт. Якщо some_thing()це посилання, воно виведе посилання.

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

Тож тепер нам потрібно додати більше до нашого синтаксису. name ::= value;означає "робити те, що decltype(auto)робить". У мене немає еквівалента для пітонічного варіанту.

Дивлячись на цей синтаксис, чи не так легко випадково ввести неправильний тип? Мало того, навряд чи це самодокументування. Навіть якщо ви ніколи раніше цього не бачили decltype(auto), він досить великий і очевидний, що ви можете принаймні легко сказати, що відбувається щось особливе. Тоді як візуальна різниця між ::=і :=мінімальна.

Але це думка; є більше предметних питань. Дивіться, все це базується на використанні синтаксису присвоєння. Ну ... як щодо місць, де ви не можете використовувати синтаксис призначення? Подобається це:

for(auto &x : container)

Ми змінюємо це на for(&x := container)? Тому що, схоже, це говорить про щось дуже різне від діапазону for. Схоже, це оператор ініціалізатора із звичайного forциклу, а не на основі діапазону for. Це також був би інший синтаксис, ніж невиведені випадки.

Крім того, ініціалізація копіювання (з використанням =) - це не те саме в C ++, що і пряма ініціалізація (з використанням синтаксису конструктора). Тому name := value;може не спрацювати у тих випадках, коли auto name(value)б мали.

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

Крім того, є ще одна річ: C ++ 14. Це дало нам одну корисну функцію вирахування: вирахування типу повернення. Але це базується на заповнювачах. Так само, як і на основі діапазону for, він заснований на імені типу, яке заповнює компілятор, а не якимсь синтаксисом, застосованим до певного імені та виразу.

Дивіться, усі ці проблеми походять з одного джерела: ви вигадуєте абсолютно новий синтаксис для оголошення змінних. Деклараціям на основі заповнювачів не потрібно було вигадувати новий синтаксис . Вони використовують точно такий самий синтаксис, як і раніше; вони просто використовують нове ключове слово, яке діє як тип, але має особливе значення. Саме це дозволяє йому працювати в залежності від діапазону forта для вирахування типу повернення. Саме це дозволяє йому мати кілька форм ( autoпроти decltype(auto)). І так далі.

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

Хіба що це було просто написання заповнювачів з різними ключовими словами чи символами ...


2
ІМХО, це єдина відповідь, яка стосується суттєвого обґрунтування вибору заповнювача. Ще одним прикладом може бути вирахування типу в загальній лямбді. Шкода, що ця відповідь отримала так мало голосів за те, що вона була опублікована трохи пізно ...
llllllllll

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

@liliscent: Дійсно, ця відповідь запізнюється для партії. Піднято. (Одним з поліпшень хоча б згадка про C ++ ідеї , що якщо що - то може бути декларація , то це є декларацією.)
Вірсавія

12

Коротше кажучи: autoв деяких випадках може бути відмовлено, але це призведе до непослідовності.

Перш за все, як зазначено, синтаксис оголошення в C ++ є <type> <varname>. Явні оголошення вимагають замість них якийсь тип або принаймні ключове слово оголошення. Отже, ми могли б використовувати var <varname>або declare <varname>щось інше, але autoце давнє ключове слово в C ++ і є гарним кандидатом для автоматичного відрахування типу ключового слова.

Чи можна неявно оголосити змінні шляхом присвоєння, не порушуючи все?

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

a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
  return 0;
}

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

a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here

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


11

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

  int x = some_function();

Можна зробити більш загальним, зробивши intтип автоматично виведеним:

  auto x = some_function();

Тож це консервативне розширення мови; він вписується в існуючий синтаксис. Без цього він x = some_function()стає заявою про присвоєння, більше не заявою.


9

синтаксис повинен бути однозначним і сумісним із зворотною стороною.

Якщо авто відкинене, не буде можливості розрізнити твердження та визначення.

auto n = 0; // fine
n=0; // statememt, n is undefined.

3
Важливим моментом є те, що autoце вже було ключове слово (але із застарілим значенням), тому воно не порушило код, використовуючи його як назву. Це є причиною, коли краще ключове слово, наприклад varабо let, не було обрано замість цього.
Фракс

1
@Frax IMO autoнасправді є досить чудовим ключовим словом для цього: він виражає саме те, що означає, а саме, замінює ім’я типу на „автоматичний тип”. З ключовим словом like varабо let, отже, вам слід вимагати ключове слово, навіть якщо тип вказаний явно, тобто var int n = 0або щось подібне var n:Int = 0. В основному так роблять у Rust.
залишилося близько

1
@leftaroundabout Незважаючи на те, що auto, безумовно, є чудовим у контексті існуючого синтаксису, я б сказав, що щось на зразок var int x = 42того, як визначення базової змінної, зі скороченнями var x = 42та int x = 42як скорочення, має більше сенсу, ніж поточний синтаксис, якщо розглядати його як поза історичним змістом. Але це головним чином справа смаку. Але, ви маєте рацію, я повинен був написати "одна з причин" замість "причина" у своєму оригінальному коментарі :)
Фракс,

@leftaroundabout: "auto насправді є досить чудовим ключовим словом для цього: воно виражає саме те, що означає, а саме, замінює ім'я типу на" автоматичний тип "" Це не відповідає дійсності. Не існує "автоматичного типу".
Гонки легкості на орбіті

@LightnessRacesinOrbit у будь-якому заданому контексті, в якому ви можете використовувати auto, існує автоматичний тип (інший, залежно від виразу).
залишилося близько

3

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

У мовах з можливістю неявного визначення змінних це може бути великою проблемою, особливо у великих системах. Ви робите одну друкарську помилку і налагоджуєте годинами, лише дізнавшись, що ненавмисно ввели змінну зі значенням нуля (або гірше) - bluevs bleu, labelvs lable... результат полягає в тому, що ви не можете по-справжньому довіряти коду без ретельної перевірки на точність імена змінних.

Просто використання використовує autoкомпілятор і супровідник, що ви маєте намір оголосити нову змінну.

Подумайте над цим, щоб уникнути такого роду кошмарів, в FORTRAN було введено вираз «неявне нічого» - і ви бачите, що воно сьогодні використовується у всіх серйозних програмах FORTRAN. Не мати його просто ... страшно.

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