Правило нуля в Сі++

Тепер, коли ми зрозуміли згенеровані компілятором функції, правило трьох і правило п’ять, давайте використаємо це, щоб задуматися про те, як використовувати функцію “= за замовчуванням”, щоб мати виразний і правильний код.

правило

Справді, C ++ 11 додав можливість вимагати від компілятора написання реалізації за замовчуванням для цих методів класу:

Але компілятор може також генерувати ці функції, навіть якщо ми не вказуємо їх в інтерфейсі. Ми побачили, що ця функція С ++ мала деякі хитросплетіння, але у наведеному вище випадку, код цілком еквівалентний цьому:

Це піднімає питання: якщо компілятор може надати реалізацію за замовчуванням, чи слід писати = default, щоб бути більш явним, навіть коли це не змінює сформований код? Або це безоплатна багатослівність? Який шлях виразніший?

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

Основні рекомендації C ++ та Р. Мартіньо Фернандес: Правило нуля

Основні вказівки C ++ дуже чітко розглядають це питання, а початкові вказівки щодо конструкторів зазначають:

C.20: Якщо ви можете уникнути визначення операцій за замовчуванням, зробіть.

Правильно. Досить чітко. Тепер, в чому полягає обгрунтування цього керівного принципу?

Причина Це найпростіший і дає найчистішу семантику. [Якщо всі члени] мають усі спеціальні функції, подальша робота не потрібна.

І далі керівництво говорить, що це відоме як “Правило нуля“.

Цей термін був придуманий Р. Мартіньо Фернандесом у дописі в блозі 2012 року (дякую Lopo та Reddit користувачеві сфера991 за розкопування допису).

Що таке правило нуля? Це виглядає так: Класи, які оголошують власні деструктори, конструктори копіювання/переміщення або оператори призначення копіювання/переміщення, повинні мати справу виключно з правом власності. Інші класи не повинні оголошувати власні деструктори, конструктори копіювання/переміщення або оператори призначення копіювання/переміщення (Правило Нуля злегка перефразоване Скоттом Майерсом).

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

За винятком того, що якщо ви уважно його розглянете, Правило Нуля нічого не говорить про конструктор за замовчуванням X (). У ньому згадуються лише 5 функцій, які в іншому випадку беруть участь у Правилі п’яти. Нагадаємо, «Правило п’яти» говорить, що якщо одна з 5 функцій управління ресурсами (конструктори копіювання/переміщення, оператори присвоєння копій/переміщень, деструктор) мала нетривіальну реалізацію, то інші, безумовно, повинні мати нетривіальну реалізацію теж.

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

Але основна настанова C ++ C.20, схоже, заохочує нас також не заявляти про це:

C.20: Якщо ви можете уникнути визначення операцій за замовчуванням, зробіть.

Все ще досить чітко.

Скотт Майерс: Правило п’яти дефолтів

Скотт Мейєрс у відповідь на "Правило нуля" пише, що це становить ризик.

Дійсно, декларування будь-якої з 5 функцій має побічний ефект на автоматичну генерацію операцій переміщення. Досить суворий побічний ефект, оскільки він вимикає автоматичну генерацію операцій переміщення. (Якщо вам цікаво, навіщо саме операції переміщення, подивіться на оновлення функцій, створених компілятором, Правила трьох і Правила П’яти).

Зокрема, якщо ви додаєте деструктор до класу:

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

Насправді, якщо ви декларуєте деструктор явно, навіть якщо ви використовуєте реалізацію, створену за замовчуванням:

Тоді клас втрачає свої операції переміщення!

Захищаючи правило нуля

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

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

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

Ми також отримуємо попередження:

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

Правило п’яти дефолтів

Натомість Скотт Мейерс аргументує інше правило, правило п’яти дефолтів: завжди оголошувати 5 функцій управління ресурсами. І якщо вони тривіальні, використовуйте = default:

Зауважте, що, як і в Основних рекомендаціях C ++, поганий конструктор за замовчуванням X () залишився поза обговоренням.

Однак, якщо ми дотримуємося Правила п’яти за замовчуванням, для конструктора за замовчуванням залишається не так багато вибору. Дійсно, якщо є принаймні ще один оголошений конструктор, компілятор не генерує конструктор за замовчуванням автоматично. І тут ми маємо не один, а два заявлені конструктори: конструктор копіювання та конструктор переміщення.

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

Тож, можливо, нам слід назвати це Правилом шести дефолтів. У всякому разі.

Хороші інтерфейси для хороших програмістів

Я не думаю, що на даний момент дебати перемогли жодна із сторін.

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

Чи слід створювати весь цей код, щоб зробити інтерфейс явним?

Для мене це зводиться до питання про що подумають програмісти в класі подивившись на його інтерфейс.

Якщо ви знаєте правила С ++, ви будете знати, що клас, який не декларує жодного з 6 методів, виражає, що він їх має усі. І якщо він оголошує їх усі, крім операцій переміщення, то це, мабуть, клас, що походить із C ++ 98, і тому він не відповідає семантиці переміщення (що, до речі, є ще одним аргументом на користь Правила Нуля: хто знає яким буде майбутнє? Можливо, в C ++ 29 буде конструктор &&&, і правило нуля буде виражати, що клас хоче за замовчуванням все, включаючи &&&).

Ризик полягає в тому, що хтось створив клас, не знаючи, що він робить, або що читач коду не знає достатньо C ++, щоб зробити висновок про те, що клас може зробити. І я не думаю, що ми повинні обтяжувати код захисною мережею 5 = стандартні функції ed для кожного типу кодової бази.

Натомість ми повинні це припустити

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

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

Це сенс огляду коду, тренінгів, щоденників, наставництва, парного програмування, книг тощо. Це інвестиція, але я думаю, що нам потрібно вирівняти код, а не навпаки.

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

На закінчення я залишу завершальне слово Арне Мерцу, який резюмував дискусію правилом, з яким усі погоджуються, "Правилом всього або нічого":

До тих пір, поки можете, дотримуйтесь Правила нуля, але якщо вам потрібно написати хоча б один із Великої п'ятірки, решта за замовчуванням.

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

Вам також може сподобатися

  • Функції, створені компілятором, правило трьох і правило п’яти
  • Поширюйте знання у своїй компанії за допомогою вашого “Щоденного C ++”
  • Які книги читати, щоб стати кращими в C++
Стати меценатом!
Поділіться цим дописом! & nbsp & nbsp & nbsp & nbspНе хочу пропускати ? Дотримуйтесь: & nbsp & nbsp

Отримайте безкоштовну електронну книгу про розумні вказівники на C ++

Отримайте безкоштовну електронну книгу з більш ніж 50 сторінок, яка навчить вас основним, середнім та вдосконаленим аспектам розумних покажчиків C ++, підписавшись на наш список розсилки! Крім того, ви також будете отримувати регулярні оновлення, щоб зробити ваш код більш виразним.