У чому причина того, що в новому коді практично не скрізь не використовується [[nodiscard]] C ++ 17?


70

C ++ 17 вводить [[nodiscard]]атрибут, який дозволяє програмістам позначати функції таким чином, що компілятор видає попередження, якщо повернутий об'єкт відкидається абонентом; той самий атрибут може бути доданий до всього типу класу.

Я читав про мотивацію цієї функції в оригінальній пропозиції , і знаю, що C ++ 20 додасть атрибут до стандартних функцій типу std::vector::empty, імена яких не передають однозначного значення відносно значення, що повертається.

Це класна і корисна функція. Насправді це майже здається занадто корисним. Скрізь, де я читав [[nodiscard]], люди обговорюють це так, ніби ви просто додали його до декількох функцій чи типів і забули про інше. Але чому нерозбірне значення має бути окремим випадком, особливо при написанні нового коду? Чи не відхилена повернена вартість, як правило, помилка чи принаймні витрата ресурсів?

І чи не є одним із принципів дизайну самого C ++, що компілятор повинен виявити якомога більше помилок?

Якщо так, то чому б не додати [[nodiscard]]власний, не застарілий код майже до кожного окремого нефункціонального voidі майже до кожного окремого типу класу?

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

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

Чому така механіка не має сенсу в новому коді C ++? Чи є багатослів’я єдиною причиною не користуватися [[nodiscard]]майже скрізь?


[*] Теоретично у вас може бути щось на зразок [[maydiscard]]атрибута, який також може бути доданий заднім числом до функцій, як printfу реалізаціях стандартних бібліотек.


22
Ну, є ціла маса функцій, де повертане значення можна відчутно відкинути. operator =наприклад. І std::map::insert.
immibis

2
@immibis: Правда. Стандартна бібліотека повна таких функцій. Але в моєму власному коді вони, як правило, надзвичайно рідкісні; ви думаєте, це питання коду бібліотеки проти коду програми?
Крістіан Хакл

9
"... жахливо багатослівний, що він починає відчувати себе Java ..." - прочитавши це у запитанні про C ++, змусив мене трохи
посмикнутися

3
@ChristianHackl Коментарі, швидше за все, не є правильним місцем для "мовлення", і, звичайно, Java також є багатослівним порівняно з іншими мовами. Але файли заголовків, оператори присвоєння, конструктори копій та правильне використання constможуть значно погіршити "простий" клас (а точніше "звичайний старий об'єкт даних") значно в C ++.
Marco13

2
@ Marco13: Я не можу вирішити стільки моментів в одному коментарі. Давайте коротко: у Java немає файлів заголовків, що зменшує кількість файлів, але збільшує зв'язок; OTOH змушує вас класти кожен клас вищого рівня у його власний файл та кожну функцію в класі. У C ++ ви майже ніколи не реалізовуєте операторів призначення та копіюєте конструктори самостійно; ці функції є більш актуальними для класів стандартних бібліотек, таких як std::vectorабо std::unique_ptrдля яких вам просто потрібні члени даних у визначенні вашого класу. Я працював з обома мовами; Java - це нормально мова, але вона є більш багатослівною.
Крістіан Хакл

Відповіді:


73

У новому коді, який не повинен бути сумісним із старими стандартами, використовуйте цей атрибут там, де це розумно. Але для C ++ [[nodiscard]]- це поганий дефолт. Ви пропонуєте:

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

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

Дизайнерські рішення для існуючої, зрілої мови з дуже великою базою коду обов'язково відрізняються від дизайнерських рішень для абсолютно нової мови. Якби це нова мова, то попередження за замовчуванням було б розумним. Наприклад, мова Nim вимагає явного відкидання непотрібних значень - це було б аналогічно загортанню кожного виразу виразу в C ++ з використанням символу (void)(...).

[[nodiscard]]Атрибут є найбільш корисним в двох випадках:

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

  • якщо значення повернення необхідно перевірити, наприклад, для C-подібного інтерфейсу, який повертає коди помилок, а не кидання. Це випадок первинного використання. Для ідіоматичного C ++ це буде досить рідко.

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

  • Розглянемо тип даних черги із .pop()методом, який видаляє елемент і повертає копію видаленого елемента. Такий метод часто зручний. Однак є деякі випадки, коли ми хочемо лише видалити елемент, не отримуючи копію. Це абсолютно законно, і попередження не буде корисним. Інший дизайн (наприклад, std::vector) розбиває ці обов'язки, але це має інші компроміси.

    Зауважте, що в деяких випадках копія повинна бути зроблена в будь-якому випадку, тому завдяки поверненню RVO копія була б безкоштовною.

  • Розглянемо плавні інтерфейси, де кожна операція повертає об'єкт, щоб можна було виконувати подальші операції. У C ++ найпоширенішим прикладом є оператор вставки потоку <<. Було б надзвичайно громіздко додати [[nodiscard]]атрибут до кожної <<перевантаження.

Ці приклади демонструють, що компіляція ідіоматичного коду C ++ без попереджень під мовою "C ++ 17 з нодискарною карткою за замовчуванням" була б досить стомлюючою.

Зауважте, що ваш блискучий новий код C ++ 17 (де ви можете використовувати ці атрибути) все ще може бути складений разом з бібліотеками, націленими на більш старі стандарти C ++. Ця зворотна сумісність має вирішальне значення для екосистеми C / C ++. Таким чином, якщо встановити nodiscard за замовчуванням, це призведе до багатьох попереджень для типових, ідіоматичних випадків використання - попереджень, які неможливо виправити без далекосяжних змін вихідного коду бібліотеки.

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


6
Я підтримав це, оскільки він містить корисну інформацію. Ще не впевнений, чи справді він відповідає на питання. Знову нечисті функції, такі як popабо operator<<, явно будуть позначені моїм уявним [[maydiscard]]атрибутом, тому ніяких попереджень не буде генеровано. Ви говорите , такі функції , як правило , набагато більше спільного , ніж я хотів би вірити, або набагато частіше , ніж в цілому в моїх власних бази коду? Ваш перший абзац про існуючий код, безумовно, правдивий, але все ще змушує мене замислитись, чи хтось зараз перецьовує весь свій новий код [[nodiscard]], а якщо ні, то чому б ні.
Крістіан Хакл

23
@ChristianHackl: Був час в історії C ++, коли вважалося хорошою практикою повертати значення як const. Ви не можете сказати returns_an_int() = 0, так навіщо returns_a_str() = ""працювати? C ++ 11 показав, що це насправді жахлива ідея, оскільки зараз весь цей код забороняв рухатись, оскільки значення були const. Я думаю, що правильний винос із цього уроку - це "не залежно від того, що робити з вашим результатом". Ігнорування результату - цілком дійсна річ, яку можуть захотіти зробити абоненти, і якщо ви не знаєте, що це неправильно (дуже висока смуга), не слід блокувати його.
GManNickG

13
Нодискард на empty()також чутливий, оскільки ім'я досить неоднозначне: це предикат is_empty()чи мутатор clear()? Зазвичай ви не очікуєте, що такий мутатор поверне щось, тому попередження про значення повернення може попередити програміста про проблему. З чіткішою назвою цей атрибут був би не таким необхідним.
амон

13
@ChristianHackl: Як вже говорили інші, я вважаю, що чисті функції є хорошим прикладом того, коли це слід використовувати. Я б пішов на крок далі і сказав, що має бути [[pure]]атрибут, і це має означати [[nodiscard]]. Таким чином зрозуміло, чому цей результат не слід відкидати. Але крім чистих функцій, я не можу придумати інший клас функцій, який повинен мати таку поведінку. І більшість функцій не є чистими, тому я не вважаю, що nodiscard за замовчуванням є правильним вибором.
GManNickG

5
@GManNickG: Після спроби і не налагоджувати код , який ігнорував коди помилок, я сильно вірю , що переважна більшість кодів помилок повинно бути перевірено за замовчуванням.
Mooing Duck

9

Причини я [[nodiscard]]майже не всюди:

  1. (Великий :) Це ввести шлях занадто багато шуму в моїх заголовках.
  2. (майор :) Мені не здається, що я повинен робити чіткі спекулятивні припущення щодо коду інших людей. Ви хочете відкинути повернене значення, яке я вам даю? Добре, вибийте себе.
  3. (неповнолітній :) Ви б гарантували несумісність із C ++ 14

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


0

Як приклад: оператор << має значення повернення, яке залежить від виклику або абсолютно необхідне, або абсолютно марне. (std :: cout << x << y, перший потрібен, оскільки він повертає потік, другий взагалі не має користі). Тепер порівняйте з printf, де кожен відкидає повернене значення, яке існує для перевірки помилок, але тоді оператор << не має перевірки помилок, тому він не може бути корисним в першу чергу. Так що в обох випадках нодискарда була б просто згубною.


Це дає відповідь на запитання, яке не було задано, тобто "У чому причина невсюдного використання [[nodiscard]]?".
Крістіан Хакл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.