Які ідіоми C ++ застаріли в C ++ 11?


192

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

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

Відповідаючи на це, ви можете пропустити очевидні речі, такі як "використовувати автоматичні змінні".


13
Ви не можете застаріти ідіоми.
— Паббі

6
Розмова Герба Саттера на Going Native 2012 охоплювала це:
— bames53

5
Повернення постійних значень більше не рекомендується. Очевидно, auto_ptrце також застаріло.
— Керрек СБ

27
Звичайно, можна, паббі. Перш ніж були винайдені шаблони C ++, існувала макро техніка робити шаблони. Потім C ++ додав їх, і старий спосіб вважався поганим.
— Алан Балуу

7
Це питання справді потрібно перенести на Programmers.se.
— Нікол Болас

Відповіді:


173
  1. Заключний клас : C ++ 11 забезпечує finalспецифікатор для запобігання похідності класу
  2. C ++ 11 лямбда значно знижують потребу в названих класах функціональних об'єктів (функторів).
  3. Move Constructor : магічні способи, коли std::auto_ptrроботи більше не потрібні завдяки першокласній підтримці посилань на rvalue.
  4. Безпечний бул : Про це йшлося раніше. Явні оператори C ++ 11 заперечують цю дуже поширену ідіому C ++ 03.
  5. Зменшення до придатності : Багато контейнерів C ++ 11 STL забезпечують функцію shrink_to_fit()члена, що повинно усунути необхідність заміни на тимчасову.
  6. Тимчасовий базовий клас : Деякі старі бібліотеки C ++ використовують цю досить складну ідіому. З семантикою руху це більше не потрібно.
  7. Введіть безпечний перелік Перерахування дуже безпечні для C ++ 11.
  8. Заборона розподілу купи : = deleteСинтаксис є набагато більш прямим способом сказати, що певна функціональність явно заперечується. Це застосовно для запобігання розподілу купи (тобто =deleteдля учасника operator new), запобігання копій, присвоєння тощо.
  9. Шаблонні typedef : Шаблони псевдонімів на C ++ 11 зменшують потребу в простих шаблонах typedef. Однак генераторам складного типу все ще потрібні метафункції.
  10. Деякі числові обчислення часу компіляції, такі як Фібоначчі, можна легко замінити, використовуючи узагальнені постійні вирази
  11. result_of: Використання шаблону класу result_ofслід замінити на decltype. Я думаю, result_ofвикористовує, decltypeколи є.
  12. Ініціалізатори членів класу зберігають введення тексту для ініціалізації за замовчуванням нестатичних членів зі значеннями за замовчуванням.
  13. У новому C ++ 11 код NULLслід переосмислити як nullptr, але дивіться розмови STL, щоб дізнатися, чому вони вирішили проти цього.
  14. Фанатики шаблонів виразів раді, що вони мають синтаксис функції зворотного типу в C ++ 11. Немає більше 30-ти рядкових типів повернення!

Я думаю, я зупинюсь на цьому!


Дякуємо за детальний матеріал!
— Алан Балуу

7
Чудова відповідь, але я б викреслив result_ofзі списку. Незважаючи на громіздкість, typenameнеобхідну до цього, я думаю, що typename result_of<F(Args...)::typeіноді простіше прочитати, ніж decltype(std::declval<F>()(std::declval<Args>()...), і з прийняттям N3436 в робочий документ вони обидва працюють для SFINAE (що раніше було перевагою, decltypeщо result_ofне пропонували)
— Джонатан Уейлі

Відносно 14) Я все ще плачу, що мені потрібно використовувати макроси, щоб написати один і той же код два рази - один раз для функції функції та один раз для заяви decltype () ...

2
Я хотів би зазначити, що ця тема пов’язана із цієї сторінки Microsoft як стаття "Для отримання додаткової інформації" в загальному вступі до мови C ++, але ця тема є вузькоспеціалізованою! Я можу запропонувати короткий текст "Ця тема НЕ для новачків C ++!" порада буде включена на початку теми чи ця відповідь?
— Аакіні

Re 12: "Ініціалізація членів класу" - це нова ідіома, а не застаріла ідіома, чи не так? Можливо, переключити порядок речень? Re 2: Функціонери дуже корисні, коли ви хочете обходити типи, а не об'єкти (особливо в параметрах шаблону). Тому застарілі лише деякі функції функторів.
— einpoklum

66

В один момент часу було стверджено, що варто повертатись за constзначенням, а не просто за значенням:

const A foo();
^^^^^

Це було в основному нешкідливим для C ++ 98/03, і, можливо, навіть зловив декілька помилок, які виглядали так:

foo() = a;

Але повернення через constпротипоказане в C ++ 11, оскільки воно гальмує семантику переміщення:

A a = foo();  // foo will copy into a instead of move into it

Тож просто розслабтесь та кодуйте:

A foo();  // return by non-const value

9
Однак тепер можна усунути помилки, які можна запобігти, використовуючи класифікатори для функцій. Такі, як у наведеному вище випадку, визначаючи A& operator=(A o)&замість A& operator=(A o). Вони запобігають нерозумним помилкам і змушують класи вести себе як базові типи і не заважають рухати семантику.
— Джо

61

Як тільки ви зможете відмовитися 0і NULLна користь nullptr, зробіть це!

У негенеричному коді використання 0чи NULLне така велика справа. Але як тільки ви починаєте обходити нульові константи покажчика в загальному коді, ситуація швидко змінюється. Коли ви переходите 0до значення, template<class T> func(T) Tвиводиться як не, intа не як нульова константа вказівника. І це не може бути перетворене назад у нульову постійну вказівник після цього. Це каскади перетворюється на тріску проблем, яких просто не існує, якщо всесвіт використовується тільки nullptr.

C ++ 11 не застаріло 0і NULLє нульовими константами вказівника. Але ви повинні кодувати так, ніби це було.


що таке decltype (nullptr)?

4
@GrapschKnutsch: Так std::nullptr_t.
— Говард Хінант

Запропонуйте це перефразовувати як застарілу ідіому, а не нову конвенцію, яку слід прийняти (наприклад, "Використання 0або NULLдля нульових покажчиків").
— einpoklum

38

Безпечна ідіома булів → explicit operator bool().

Приватні конструктори копій (прискорення: без копіювання) → X(const X&) = delete

Моделювання остаточного класу з приватним деструктором та віртуальним успадкуванням →class X final


хороші та стислі приклади, один з яких навіть несе в собі слово "ідіома". добре кажучи
— Себастьян Мах

2
Нічого раніше, я ніколи не бачив "безпечної ідіоми", це виглядає досить огидно! Я сподіваюся, що він мені ніколи не знадобиться в коді попереднього С ++ 11 ...
— хлопці

24

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

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

Як тільки ви використовуєте компілятор C ++ 11 з повною стандартною бібліотекою C ++ 11, у вас більше немає хорошого приводу не використовувати стандартні алгоритми для побудови своїх . Лямбда просто вб'є.

Чому?

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

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

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


8
Насправді є хороший привід. Ви використовуєте алгоритми Boost.Range , які набагато приємніші;)
— Nicol Bolas

10
Я не бачу, що for_eachз лямбда є кращим, ніж еквівалентний діапазон на основі циклу, із вмістом лямбда в циклі. Код виглядає більш-менш однаково, але лямбда вводить деякі додаткові розділові знаки. Ви можете використовувати еквіваленти речей, як-от boost::irangeзастосувати його до більшої кількості циклів, ніж просто тих, які, очевидно, використовують ітератори. Плюс діапазон, що базується на циклі, має більшу гнучкість, оскільки ви можете вийти рано, якщо це потрібно ( returnабо за його допомогою break), тоді як з цим for_eachвам потрібно буде кинутись.
— Стів Джессоп

5
@SteveJessop: Незважаючи на це, наявність діапазону на основі діапазону forзмушує звичайну it = c.begin(), const end = c.end(); it != end; ++itідіому не виключати.
— Ben Voigt

7
@SteveJessop Однією з переваг for_eachалгоритму над діапазоном, заснованим на циклі, є те, що ви не можете break або return. Тобто, коли ти бачиш for_each, що відразу знаєш, не дивлячись у тіло, що такої хитрості немає.
— bames53

5
@Klaim: щоб бути конкретним, я порівнюю, наприклад, std::for_each(v.begin(), v.end(), [](int &i) { ++i; });з for (auto &i : v) { ++i; }. Я погоджуюсь, що гнучкість є двосхилою ( gotoдуже гнучка, в цьому проблема). Я не думаю , що обмеження не в змозі використати breakв for_eachверсії компенсуватися додаткового багатослів'я вона вимагає - користувачі for_eachтут IMO жертвуючи реально читаність і зручність для свого роду теоретичного поняття про те , що for_eachце в принципі ясно і концептуально простіший. На практиці це не зрозуміліше чи простіше.
— Стів Джессоп

10

Вам потрібно буде впроваджувати власні версії swapрідше. У програмі C ++ 03 ефективне некидання swapчасто необхідно для уникнення затрат та перекидання копій, а оскільки std::swapвикористовує дві копії, swapчасто доводиться налаштовувати. У C ++ std::swapвикористовує move, і тому фокус зміщується на впровадженні ефективних конструкторів переміщення та некидальних переміщень та операторів присвоєння переміщення. Оскільки для них за замовчуванням часто просто добре, це буде набагато менше роботи, ніж у C ++ 03.

Як правило, важко передбачити, які ідіоми будуть використовуватися, оскільки вони створені через досвід. Ми можемо очікувати, що "Ефективний C ++ 11", можливо, наступного року, а "Стандарти кодування C ++ 11" лише через три роки, оскільки необхідного досвіду ще немає.


1
Я сумніваюся в цьому. Рекомендованим стилем є використання swap для переміщення та копіювання, але не std :: swap, оскільки це було б круговим.
— Алан Балуу

Так, але конструктор ходу зазвичай викликає користувацький своп, або він по суті еквівалентний.
— Зворотний

2

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

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

Це дозволило уникнути будь-якого копіювання за рахунок копіювання elision у поєднанні з swapвищезазначеним.


1
У вашому прикладі swap непотрібний, копія elision будує повернене значення в mapбудь-якому випадку. Показана вами техніка корисна, якщо вона mapвже існує, а не просто побудована. Приклад був би кращим без коментаря "дешевого конструктора за замовчуванням" та з "// ..." між цією конструкцією та свопом
— Джонатан Уейклі

Я змінив це згідно вашої пропозиції. Дякую.
— Анджей

Використання "великих" та "більших" заплутано. Чому б не пояснити, як мають значення розміри ключа та типу значення?
— einpoklum

1

Коли я помітив, що компілятор, що використовує стандарт C ++ 11, більше не має помилок у наступному коді:

std::vector<std::vector<int>> a;

за нібито містить оператора >>, я почав танцювати. У попередніх версіях треба було б зробити

std::vector<std::vector<int> > a;

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

Я, однак, не знаю, чи це було для вас "очевидно".


1
Ця функція була додана вже в попередньому C ++. Або, принаймні, Visual C ++ впроваджував це на обговоренні стандартів багато років раніше.
— Алан Балуу

1
@AlanBaljeu Звичайно, у компілятор / бібліотеки додається багато нестандартних речей. Перед C ++ 11 було багато компіляторів, які мали «автоматичне» змінне оголошення, але тоді ви не могли бути впевнені, що ваш код насправді може бути скомпільований чим-небудь іншим. Питання стосувалося стандарту, а не про те, "чи був якийсь компілятор, який міг би це зробити".
— v010дя

1

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


... але яка ідіома була застаріла?
— einpoklum

Не ідіома, але це вже не потрібна практика. Навіть із підтримуваним компілятором RVO, який не є обов'язковим. en.wikipedia.org/wiki/Return_value_optimization "На ранніх етапах еволюції C ++ нездатність мови ефективно повернути об'єкт класового типу з функції вважалася слабкістю ....." struct Data {char bytes [ 16]; }; void f (Дані * p) {// генерують результат безпосередньо в * p} int main () {Дані d; f (& d); }
— Мартін

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