Які ідіоми 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.