Какие идиомы C ++ устарели в C ++ 11?

192

С новым стандартом появляются новые способы ведения дел, и многие из них более хороши, чем старые, но старый способ все еще в порядке. Также ясно, что новый стандарт официально не очень устарел по причинам обратной совместимости. Итак, вопрос, который остается:

Какие старые способы кодирования определенно уступают стилям C ++ 11, и что мы можем теперь вместо этого делать?

Отвечая на это, вы можете пропустить очевидные вещи, такие как «использовать автоматические переменные».

Алан Бальжеу
источник
13
Вы не можете отвергать идиомы.
Пабби
6
Выступление
5
Возвращать постоянные значения больше не рекомендуется. Очевидно, также auto_ptrне рекомендуется.
Kerrek SB
27
Конечно, ты можешь, Пабби. До того, как были изобретены шаблоны C ++, для создания шаблонов была методика макросов. Затем C ++ добавил их, и старый способ считался плохим.
Алан Бальжеу
7
Этот вопрос действительно нужно перенести на Programmers.se.
Николь Болас

Ответы:

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

Я думаю, что я остановлюсь там!

Sumant
источник
Спасибо за подробную информацию!
Алан Бальеу
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к a 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() .

Конструкторы частных копий (boost :: noncopyable) → X(const X&) = delete

Имитация финального класса с частным деструктором и виртуальным наследованиемclass X final

kennytm
источник
хорошие и сжатые примеры, один из которых даже несет в себе слово «идиома». хорошо поставленный
Себастьян Мах
2
Ух ты, я никогда раньше не видел «идиому безопасного булла», это выглядит довольно отвратительно! Надеюсь, мне это никогда не понадобится в коде до C ++ 11 ...
мальчик
24

Одна из вещей, которая просто заставляет вас избегать написания базовых алгоритмов на C ++ 11, - это доступность лямбд в сочетании с алгоритмами, предоставляемыми стандартной библиотекой.

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

Как только вы используете компилятор C ++ 11 с полной стандартной библиотекой C ++ 11, у вас больше нет веских оправданий, чтобы не использовать стандартные алгоритмы для построения своих . Лямбда просто убей это.

Зачем?

На практике (после того, как я сам использовал этот способ написания алгоритмов), гораздо проще читать что-то, построенное с простыми словами, означающими то, что сделано, чем с некоторыми циклами, которые вы должны расшифровать, чтобы узнать значение. Тем не менее, автоматическое определение лямбда-аргументов очень поможет сделать синтаксис более простым для сравнения с необработанным циклом.

По сути, алгоритмы чтения, сделанные с помощью стандартных алгоритмов, намного проще, чем слова, скрывающие детали реализации циклов.

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

Klaim
источник
8
На самом деле есть хорошее оправдание. Вы используете алгоритмы Boost.Range , которые намного приятнее;)
Никол Болас
10
Я не вижу, чтобы for_eachлямбда-код был лучше, чем эквивалентный цикл for, основанный на диапазоне, с содержанием лямбды в цикле. Код выглядит более или менее одинаково, но лямбда вводит дополнительную пунктуацию. Вы можете использовать эквиваленты таких вещей, как boost::irangeприменять его к большему количеству циклов, чем те, которые явно используют итераторы. Плюс цикл for, основанный на диапазоне, обладает большей гибкостью, так как вы можете выйти досрочно, если это необходимо (в порядке returnили на время break), тогда как for_eachвам нужно будет бросить.
Стив Джессоп
5
@SteveJessop: Несмотря на это, доступность на основе диапазона forделает обычную it = c.begin(), const end = c.end(); it != end; ++itидиому несуществующей.
Бен Фойгт
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» только через три года, потому что необходимого опыта еще нет.

Philipp
источник
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
}

Это позволило избежать копирования благодаря исключению копирования в сочетании с swapвышеизложенным.

Andrzej
источник
1
В вашем примере подкачка не нужна, копия elision создаст возвращаемое значение в mapлюбом случае. Техника, которую вы показываете, полезна, если она mapуже существует, а не просто создается. Пример был бы лучше без комментария «дешевый конструктор по умолчанию» и с «// ...» между этой конструкцией и свопом
Джонатан Уэйкли
Я изменил его согласно вашему предложению. Спасибо.
Анджей
Использование «большой» и «большой» сбивает с толку. Почему бы не объяснить, как важны размеры ключа и тип значения?
einpoklum
1

Когда я заметил, что компилятор, использующий стандарт C ++ 11, больше не нарушает следующий код:

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

для якобы содержащего оператора >> я начал танцевать. В более ранних версиях нужно было бы сделать

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

Что еще хуже, если вам когда-либо приходилось отлаживать это, вы знаете, насколько ужасны сообщения об ошибках, которые появляются из-за этого.

Я, однако, не знаю, было ли это "очевидным" для вас.

v010dya
источник
1
Эта функция была добавлена ​​уже в предыдущем C ++. Или, по крайней мере, Visual C ++ реализовал это в соответствии со стандартами много лет назад.
Алан Бальёу,
1
@AlanBaljeu Конечно, в компилятор / библиотеки добавляется много нестандартных вещей. Существовали тонны компиляторов, у которых было «автоматическое» объявление переменных до C ++ 11, но тогда вы не могли быть уверены, что ваш код действительно может быть скомпилирован с помощью чего-либо еще. Вопрос был о стандарте, а не о «был ли какой-нибудь компилятор, который мог бы сделать это».
v010dya
1

Возврат по значению больше не проблема. С семантикой перемещения и / или оптимизацией возвращаемого значения (зависит от компилятора) функции кодирования более естественны без затрат и затрат (большую часть времени).

Мартин А
источник
... но какая идиома устарела?
einpoklum
Не идиома, но это была хорошая практика, которая больше не нужна. Даже с поддерживаемым компилятором RVO, который не является обязательным. en.wikipedia.org/wiki/Return_value_optimization "На ранних этапах эволюции C ++ неспособность языка эффективно возвращать объект типа класса из функции считалась слабостью ....." struct Data {char bytes [ 16]; }; void f (Data * p) {// генерировать результат непосредственно в * p} int main () {Data d; F (& г); }
Martin A
Я намекал, что вы должны сформулировать свой ответ как «обычай избегать возврата по стоимости больше не актуален, как и т. Д. И т. Д.»
einpoklum