Туман рассказ на дзен часть 7

Часть 1. вступление часть 6. специфика googleчасть 7. ещ возможности cчасть 8. именование это последняя переведнная часть руководства google

Часть 1. Вступление

Часть 6. Специфика Google
Часть 7. Ещё возможности C++
Часть 8. Именование


Это последняя переведённая часть руководства Google по стилю в C++.
Спасибо за замечания и комментарии к переводу.
Надеюсь это руководство будет полезным подспорьем для программистов на C++.
Исходная статья (fork на github), обновляемый перевод.
И ещё здесь много букв.

Ещё возможности C++

Rvalue-ссылки

Используйте rvalue-ссылки:

  • Для объявления конструкторов перемещения и операторов перемещения.
  • Для объявления перегружаемых функций с const& и && аргументами если это обеспечит значительное улучшение производительности против передачи по значению или если пишется код с небольшими накладными расходами или поддержкой произвольных типов. Избегайте увеличения количества перегружаемых функций (обычно бывает, когда комбинируются типы для несколько параметров).
  • Для поддержки ‘perfect forwarding’ в ‘универсальном’ коде.

Определение
Rvalue-ссылка является ссылочным типом, привязанным к временному объекту. По синтаксису похожа на обычную ссылку. Например, void f(std::string&& s); объявляет функцию с аргументом rvalue-ссылка на std::string.

Когда суффикс ‘&&’ (без дополнительных квалификаторов) используется с шаблонным аргументом функции, то применяются специальные правила определения типа аргумента. И такая ссылка имеет название forwarding reference.

За

  • Определение конструктора перемещения (принимающего rvalue-ссылку на тип класса) даёт возможность переместить (move) класс вместо его копирования. Например, если v1 это std::vector<std::string>, то код auto v2(std::move(v1)) скорее всего выполнит несколько операций с указателями вместо копирования большого объёма данных. И в большинстве случаев это приведёт к существенному увеличению производительности кода.
  • Rvalue-ссылки позволяют реализовать типы, которые можно перемещать, а не копировать. Это полезно для типов, которые нельзя копировать, но которые хочется передавать в функцию как аргумент, хранить в контейнере и т.д.
  • Функция std::move необходима для эффективного использования некоторых типов стандартной библиотеки, таких как std::unique_ptr.
  • Forwarding references, использующие объявление rvalue-ссылки, позволяют написать единую обёртку, перемещающую аргумент в другую функцию. И это одинаково работает вне зависимости от того, временный объект или нет, константный он или не очень. Это и называется ‘perfect forwarding’.

Против

  • Rvalue-ссылки не все хорошо понимают. Особенности, наподобие схлопывания ссылок или специальных правил определения типа, всё сильно усложняют.
  • Rvalue-ссылки часто используют неправильно, т.к. они интуитивно нелогичны: обычно ожидается, что аргумент будет иметь валидное состояние после вызова функции (и операции перемещения выполняться не будут).

Вердикт
Вы можете использовать rvalue-ссылки чтобы объявить конструктор перемещения и перемещающий оператор присваивания (как описано в Копируемые и перемещаемые типы). См. также C++ Primer для более подробной информации о семантике перемещения и, также, std::move.

Вы можете использовать rvalue-ссылки чтобы объявить пары перегружаемых функций, одна с Foo&&, другая с const Foo&. Обычно программисты предпочитают передавать аргументы по значению. Однако использование пары функций может дать лучшую производительность, или позволит в обобщённом коде поддержать большое количество типов. И не забывайте: если ради производительности пишется более сложный код, проверьте и убедитесь, что это действительно помогает.

Вы можете использовать ‘forwarding references’ вместе с std::forward
, чтобы реализовать ‘perfect forwarding’.

Дружественные сущности

В ряде случаев допустимо использовать классы и функции как friend.

Дружественные типы обычно определяются в том же файле, поэтому нет необходимости открывать другой файл, чтобы разобраться с использованием закрытых членов класса. Обычное использование friend: когда класс FooBuilder объявляется дружественным (friend) классу Foo, так что FooBuilder может корректно настроить внутреннее состояние Foo без необходимости открывать это состояние всем остальным. В ряде случаев удобно сделать класс unit-тестов дружественным исходному классу.

Дружественность расширяет (но не ломает) инкапсуляцию класса. В ряде случаев, когда требуется дать доступ к внутреннему состоянию только одному классу, лучше объявить его как friend, чем делать дополнительные открытые члены класса. Однако, в остальном классы должны взаимодействовать только через открытые функции.

Исключения (программные)

Мы НЕ используем исключения C++.
За

  • Исключения позволяют обрабатывать ситуации типа «это невозможно» не в месте возникновения ошибки, а на более верхнем уровне, позже. И всё это без копания в исходниках и составления таблиц с кодами ошибок.
  • Исключения используются в большинстве современных языков программирования и их использование в C++ позволяет писать код, концептуально схожий с Python, Java и др.
  • Некоторые библиотеки C++ используют исключения в своей работе и отказ от них может существенно усложнить интеграцию с этими библиотеками.
  • Исключения являются единственным способом проинформировать о проблемах в конструкторе класса. Конечно, это можно обойти, используя фабричные методы или метод Init(), однако это потребует либо дополнительного выделения памяти или, соответственно, обработку специального «невалидного» состояния.
  • Исключения очень удобны при использовании в тестировании (в тестовых фреймворках).

Против

  • Если используется throw в функции, то вы должны проверить всех, кто эту функцию вызывает. Должна быть базовая гарантия безопасности при исключениях. Или же код никогда не ловит исключения и тогда программа может внезапно завершиться. Например есть код, где f() вызывает g(), который вызывает h(). Если h выбрасывает исключение, которое отлавливает f, то g нужно писать аккуратно, иначе могут быть утечки ресурсов и т.д.
  • Обычно использование исключений усложняет отслеживание последовательности выполнения кода. Например, функции могут завершаться в неожиданных местах. Это затрудняет поддержку кода и его отладку. Вы можете улучшить ситуацию, следуя (своим) правилам когда и где можно использовать исключения. Однако, очень желательно, чтобы и другие разработчики знали об этих правилах.
  • Безопасное использование исключений требует использования дополнительных принципов кодирования, например, RAII. И их количество (принципов кодирования) может быть значительным. Например, чтобы разработчик не разбирался в тонкостях всей цепочки вызовов неизвестного ему кода, желательно выделить код сохранения данных в хранилище в отдельную фазу-фиксацию. Такое выделение может нести как плюсы, так и минусы (э-э-э, корпоративная политика смешивания кода в одну кучу для удобства обсфуркации?). В любом случае, использование исключений уменьшает количество доступных вариантов.
  • Использование исключений ведёт к распуханию бинарного файла программы, увеличивает время компиляции (иногда только чуть-чуть) и вообще может привести к проблемам с адресным пространством.
  • Само наличие исключений может провоцировать разработчиков выбрасывать их по поводу и без повода, даже когда это не нужно или может привести к сложностям в обработке. Например, если пользователь вводит неподходящий текст, это не должно приводить к выбрасыванию исключения. И вообще, если всё это расписывать, то никакого документа не хватит!

Вердикт
Для новых проектов преимуществ от использования исключений обычнобольше, чем недостатков. Однако, для уже существующего кодавведение исключений может повлиять на весь код. Также могут возникнутьпроблемы стыка нового кода и старого (без исключений) кода.

Т.к. большинство C++ кода в Google не использует исключений, то очень проблематично будет внедрять новый код, который будетгенерировать исключения. Существующий код в Google не может корректно работать с исключениями, поэтому цена внедрения исключений намного выше, чем реализация любого нового проекта. Переписывание существующего кодапод обработку исключений — это будет очень медленный процесс, с большим количеством ошибок. Поэтому лучше использовать альтернативу в виде возврата кода ошибки и assert-ов: это не так сложно.

Этот запрет также распространяется на добавленные в C++11 возможности, такие как std::exception_ptr и std::nested_exception.

Однако, для кода под Windows есть послабления.

noexcept

Указывайте noexcept, если это корректно и будет полезно.
Определение
Спецификатор noexcept используется для указания, что функция не будет выбрасывать исключения. Если же функция с таким спецификаторомвсё же выбросит исключение, то произойдёт крэш программы через std::terminate.

Также есть оператор noexcept. Он выполняет проверку: объявлено ли выражениекак «не выбрасывающее исключений». Проверка проводится на этапе компиляции.
За

  • Спецификация конструктора перемещения как noexcept может улучшить производительность в ряде случаев, например std::vector<T>::resize() скорее переместит объект нежели скопирует его, если конструктор перемещения для типа T заявлен как noexcept.
  • Указание noexcept для функций может разрешить дополнительную оптимизацию, например компилятор может не генерировать код по раскрутке стека. Конечно, это имеет смысл, если в целом исключения разрешены.

Против

  • Если проекты следуют данному руководству и в них исключения запрещены, то очень сложно проверить правильность спецификатора noexcept, если вообще применимо понятие правильности.
  • Вы не можете безболезненно удалить ранее прописанный спецификатор noexcept, т.к. это аннулирует гарантию, на которую может полагаться другой код, причём полагаться весьма замысловатыми способами.

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

Используйте безусловный noexcept, если исключения полностью запрещены (т.е. в типовом проекте C++ в Google).В ином случае, используйте спецификатор noexcept сусловиями (желательно простыми), которые становятся false в тех редких случаях, когда функция может всё-таки выбросить исключение.Эти тесты могут пользоваться проверками на характеристики типов(например, std::is_nothrow_move_constructible дляобъектов создаваемых через конструктор перемещения) или аллокаторов(например, absl::default_allocator_is_nothrow). Отметим, что наиболее частая причина исключений — невозможностьвыделения памяти (и да, мы верим, что это не относится к конструкторамперемещения — они не должны выбрасывать исключений из-за ошибок выделения памяти) и есть много приложений, для которых эта ситуацияозначает фатальную ошибку, которую даже не имеет смысла обрабатывать.И даже в других, потенциально ошибочных, ситуациях рекомендуетсяделать упор на простоту интерфейса, нежели на поддержку всех сценариевобработки ошибок: например, вместо написания накрученного noexceptс зависимостью от внешней хэш-функции (выбрасывает она исключения или нет), можно просто задокументировать, что разрабатываемый компонент не поддерживает хэш-функции, которые выбрасывают исключения. И, после этого, использовать noexcept без всяких дополнительных условий.

Информация о типе во время выполнения (RTTI)

Не используйте информацию о типе во время выполнения (RTTI).
Определение
RTTI позволяет запросить информацию о C++ классе объектаво время выполнения. Делается через typeid илиdynamic_cast.
За
Типовые альтернативы вместо RTTI (описано ниже)требуют модификации или редизайна иерархии классов, участвующих в запросах. Иногда такую модификацию очень тяжело сделать, или она нежелательна, особенно в коде, который уже используется в других проектах.

RTTI может быть полезен для юнит-тестов. Например, можнотестировать классы-фабрики на правильность сгенерированного типа.Также это полезно в выстраивании связей между объектами и ихмакетами (mock).

RTTI бывает полезно при работе с абстрактными объектами. Например:

bool Base::Equal(Base* other) = 0;
bool Derived::Equal(Base* other) {
  Derived* that = dynamic_cast<Derived*>(other);
  if (that == nullptr)
    return false;
  ...
}

Против
Часто сам запрос типа объекта в процессе выполнения означает проблемы с дизайном приложения, показывает наличие изъянов виерархии классов.

Бесконтрольное использование RTTI усложняет поддержку кода.Это может привести в развесистым условиям, которые зависят от типа объекта, которые рассыпаны по всему коду. И которые придётся досконально изучать если будет необходимость что-то изменить в этом коде.

Вердикт
Использование RTTI может легко привести к злоупотреблениям, поэтому будьте аккуратны. Старайтесь ограничить использование RTTI только юнит-тестами. Рекомендуется отказаться от RTTIв новом коде. Если же требуется написать код, которыйведёт себя по разному в зависимости от типа объекта, возможно следующие альтернативы будут более подходящими:

  • Виртуальные методы. Это предпочтительный способ для выполнения различного кода в зависимости от типа объекта. И вся работа (код) делается в самом объекте.
  • Если код должен находится вне объектов, то можно использовать подходы, аналогичные двойной диспетчеризации, например шаблон проектирования Посетитель/Visitor. Это позволит внешнему коду самому определять тип объектов (используя систему типов).

Когда логика программы гарантирует, чтополученный указатель на базовый класс фактически есть указательна определённый производный класс, тогда можно свободно использоватьdynamic_cast. Правда, в этом случае лучше использоватьstatic_cast.

Большое количество условий, основанных на типе объекта, естьпоказатель явных проблем в коде.

if (typeid(*data) == typeid(D1)) {
  ...
} else if (typeid(*data) == typeid(D2)) {
  ...
} else if (typeid(*data) == typeid(D3)) {
...

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

И пожалуйста, не изобретайте собственный велосипед на замену RTTI. Аргументы против собственного решения будут такие же (см. выше), да и разбираться в чужих велосипедах обычно сложнее.

Приведение типов / Casting

Рекомендуется использовать приведение типов в C++-стиле: static_cast<float>(double_value). Также можно использовать инициализацию значением в скобках для преобразования арифметических типов: int64 y = int64{1} << 42. Избегайте конструкций вида int y = (int)x или int y = int(x) (хотя последний вариант допустим при вызове конструктора класса).

Определение
В C++ приведение типов расширяется по сравнению с чистым C путём добавления операций приведения.

За
Основная проблема с приведением типов в чистом C — неоднозначность операции. Например, одинаково записанная операция: (int)3.5 и (int)«hello» сильно отличается по смыслу. Инициализация в скобках и операции в стиле C++ часто помогают избежать такой неоднозначности. Дополнительная плюшка: операции приведения в стиле С++ легче искать по коду.

Против
C++-стиль довольно громоздкий.

Вердикт
Избегайте использования приведения типов в стиле чистого C. Вместо этого используйте стиль C++ когда требуется явное преобразование типов.

  • Используйте инициализацию в скобках для преобразования арифметических типов (int64{x}). Это самый безопасный способ, т.к. в других случаях (при потере информации при конвертации) код может не скомпилироваться. Плюс лаконичный синтаксис.
  • Используйте static_cast как эквивалент преобразований в C-стиле или когда необходимо преобразовать указатель на дочерний класс в указатель на базовый и обратно (указатель на базовый класс преобразовать в указатель на дочерний (и желательно, чтобы сам объект был экземпляром дочернего класса)).
  • Используйте const_cast чтобы убрать квалификатор const (см. const).
  • Используйте reinterpret_cast чтобы небезопасно преобразовать указатели к целым числам или указателям другого типа. Используйте эту операцию только если вы знаете, что делаете и понимаете проблемы с выравниванием. Также, можете использовать в таких случаях absl::bit_cast.
  • Используйте absl::bit_cast для приведения «сырых» битов в другой тип такого же размера (например, если требуется интерпретировать double как int64).

Также может быть полезным раздел RTTI с описанием dynamic_cast.

Потоки / Streams

Используйте потоки в подходящих случаях, особенно если их использование упрощает код. Перегружайте операцию << только для типов-значений и выводите в поток собственно данные (доступные пользователю). Не выводите в поток внутренние переменные (инварианты и т.д.) и другие детали реализации.

Определение
Потоки являются стандартной абстракцией для ввода/вывода в C++, (см. стандартный заголовочный файл <iostream>). Они часто используются в коде в Google, особенно для отладочного логирования и диагностики.

За
Операторы << и >> реализуют форматированный ввод/вывод, они просты в понимании, портабельны, расширяемы и повторно используемы. Противоположность к ним — printf, который даже не поддерживает работу с std::string. Кроме того не работает с пользовательскими типа и с портабельностью там проблемы. Кроме того, printf вынуждает выбирать среди похожих версий одной функции и ориентироваться в десятках форматных символах.

Потоки обеспечивают хорошую поддержку консольного ввода/вывода через std::cin, std::cout, std::cerr и std::clog. Функции из C API тоже хорошо работают, однако могут требовать вручную буферизировать ввод.

Против

  • Форматирование потоков можно настроить через манипуляторы, изменяющие состояние потока. Учтите, применение манипуляторов сохраняется во времени и, как результат, поведение кода зависит от истории использования потока (или необходимо будет всё восстанавливать в известное состояние после каждого случая возможного применения манипуляторов). Кроме того, пользовательский код может (помимо модификации встроенных параметров) создавать собственные манипуляторы, добавлять переменные состояния и изменять поведение через функционал register_callback.
  • Проблематично полностью контролировать вывод потока: смена настроек (см. выше), вывод мешанины из параметров и собственно данных, использование перегрузки операторов (и компилятор может выбрать не ту перегрузку, которая предполагалась) — всё это не добавляет управляемости.
  • Формирование вывода посредством вызова цепочки операторов << затрудняет локализацию, т.к. при этом жёстко, прямо в коде, фиксируется порядок слов. И средства поддержки локализации лишаются ценности.
  • Потоковый API может быть сложным, с тонкостями. И программисты должны уметь с ним работать, чтобы код был эффективным.
  • Выбор правильной версии оператора << из большого количества аналогичных является для компилятора затратным. При частом их использовании в большой кодовой базе, время парсинга и анализа может отъедать до 20% времени.

Вердикт
Используйте потоки только если они являются наилучшим решением. Обычно это варианты ввода/вывода в человекочитаемый формат, предназначенный для разработчиков, а не для конечного пользователя. Также не забывайте, что в проекте уже могут быть устоявшиеся методы ввода/вывода — старайтесь использовать их. В частности, библиотеки для логирования и диагностического вывода обычно предпочтительнее нежели std::cerr или std::clog. И вместо std::stringstream лучше использовать absl/strings или их эквивалент.

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

Если потоки всё же используются, старайтесь избегать API работы с состояниями, кроме состояния ошибки. Т.е. не используйте imbue(), xalloc() и register_callback(). Рекомендуется использовать явные функции форматирования (см. absl/strings) вместо манипуляторов или флагов форматирования для таких вещей как смена основания системы счисления, точности или набивка нулями до нужного размера чисел.

Перегружайте оператор << только для типа-значения с тем, чтобы оператор выводил человеко-читаемое представление. Не выводите в поток детали реализации или внутренние переменные. Если же требуется отладочная печать внутреннего состояния, то используйте обычные функции-методы (например, метод класса DebugString() — подходящий вариант).

Преинкремент и предекремент

Используйте префиксные формы (++i) инкремента и декремента над итераторами и другими шаблонными объектами.

Определение
Когда переменная инкрементируется (++i, i++) или декрементируется (—i, i—), а возвращаемое значение не используется, то необходимо чётко понимать: использовать префиксную форму (++i, —i) или постфиксную (i++, i—).

За
Когда возвращаемое значение игнорируется, то префиксная форма не менее эффективна постфиксной (чаще префиксная более эффективна). Связано это с тем, что постфиксная реализация должна сделать копию переменной с исходным значением для возврата результата. Если переменная является итератором или другим сложным типом, то копирование её может быть затратным по ресурсам. Поэтому, если обе формы ведут себя одинаково (возвращаемое значение игнорируется), почему бы не использовать всегда префиксную форму?

Против
Традиционно раньше в разработке (особенно на языке C) использовалась постфиксная форма, особенно для циклов for. Иногда программистам легче читать код с постинкрементом, т.к. «субъект» (i) стоит перед «действием» (++), как в английском предложении.

Вердикт
Для простых скалярных (не-объектов) типов нет особой разницы в использовании префиксной или постфиксной формы. Для итераторов и других шаблонных типов используйте префиксную форму.

Использование const

В API используйте const когда это имеет смысл. В ряде случаев constexpr будет лучшей альтернативой const.

Определение
При объявлении переменных или параметров вначале может быть указано const, чтобы показать что переменные не изменяются (например, const int foo). Функции класса могут быть с квалификатором const, чтобы показать, что эта функция не изменяет состояние членов класса (например, class Foo { int Bar(char c) const; };).

За
Позволяет легко понять, как использовать переменные. Компиляторам даёт возможность полнее контролировать типы и, теоретически, генерировать лучший код. Использование констант даёт дополнительную защиту (уверенность) в корректности кода: функции не могут модифицировать переменные, изменять состояние класса и, как результат, можно безопасно работать без локов в многопоточном окружении.

Против
Использование const оно «заразное»: если передаётся const переменная в функцию, то она должна в прототипе иметь указание на const (или придётся делать const_cast). И это может быть проблемой при вызове библиотечных функций.

Вердикт
Настоятельно рекомендуется использовать const в API (параметры функций, методы, не-локальные переменные), где это имеет смысл. Такой подход даёт понятное (и верифицируемое компилятором) описание как можно модифицировать объекты. Чёткое разделение на модифицирующие (запись) и не-модифицирующие (только чтение) операции очень полезно, особенно для написания потокобезопасного кода. В частности:

  • Если функция не меняет аргумент, переданный по ссылке или указателю, то он должен быть ссылкой на константу (const T&), либо указателем на константу (const T*).
  • Если параметр передаётся по значению, то использование const не даёт никакого эффекта. Поэтому не рекомендуется объявлять константный параметр. См. также TotW #109.
  • Старайтесь объявлять члены класса константными (const) если они не изменяют логическое состояние объекта (и не дают возможности другим что-то менять, например через возврат не-константной ссылки). В противном случае эти методы могут быть небезопасными в многопоточном окружении.

Использование const для локальных переменных отдаётся на усмотрение программиста: можно использовать, можно — нет.

Все const операции класса должны работать корректно при одновременном вызове нескольких функций. Если это не выполняется, то класс должен быть явно описан как «потоко-не-безопасный».

Местоположение const

Иногда используется форма int const *foo вместо const int* foo. Обосновывается это тем, что первая форма более логична: const следует за описываемым объектом. Однако, такая «логичность» имеет мало смысла (и обычно не применяется в коде с несколькими вложенными маркерами «указатель»), т.к. чаще всего есть только один const для базового значения. В таком случае нет необходимости специально заботиться о логичности. Размещение же const вначале делает код более читабельным и согласованным с английским языком: прилагательное (const) стоит перед существительным (int).

Так что расположение const вначале является предпочтительным. Однако, это не жёсткое условие и если остальной код в проекте использует другой порядок — следуйте за кодом!

Использование constexpr

Используйте constexpr для определения констант или чтобы сделать константную инициализацию.

Определение
Переменные можно объявлять как constexpr для указания на константу, значение которой определяется во время компиляции или линковки. Также можно объявлять функции и конструкторы как constexpr, чтобы их можно было использовать для определения переменной с constexpr.

За

constexpr позволяет определять выражения с плавающей запятой (помимо литералов), использовать константы для пользовательских типов и вызовов функций.

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

Вердикт

constexpr позволяет определить неизменяемые части интерфейса. Используйте constexpr чтобы определить константы и функции для задания им значений. Не используйте constexpr, если это потребует усложнения кода. Не используйте constexpr, чтобы сделать код «встраиваемым» (inlining).

Целочисленные типы

Среди встроенных целочисленных типов C++ необходимо использовать int. Если в программе требуется переменная другого размера, то используйте целочисленные типы фиксированной длины из <stdint.h>, такие как int16_t. Если переменной нужно хранить значения, равные или превышающие 2^31 (2GiB), используйте 64-битный тип int64_t. При оценке размера не забудьте, что в int должен укладываться не только результат, но и промежуточные значения при вычислениях. И, если сомневаетесь, используйте тип подлиннее.

Определение
C++ не уточняет размер целочисленных типов, таких как int. Обычно считается, что short содержит 16 битов, int — 32, long — 32 и long long содержит 64 бита.

За
Унификация в коде.

Против
Размеры целочисленных типов в C++ могут изменяться в зависимости от компилятора и архитектуры.

Вердикт
В <cstdint> определяются различные типы: int16_t, uint32_t, int64_t и т.д. Если требуются типы фиксированного размера, то не используйте short, unsigned long long и им подобные. Из целочисленных типов языка C можно использовать только int. Также, в соответствующих случаях, используйте size_t и ptrdiff_t.

Тип int используется очень часто, особенно для небольших значений, например как счётчики в циклах. Можете считать, что int содержит минимум 32 бита (но не больше). Если требуется 64 битный целочисленный тип, то используйте int64_t или uint64_t.

Для типа который может хранить «большие значения» используйте int64_t.

Старайтесь не использовать беззнаковые числа (например, uint32_t). Допустимое применение беззнаковых чисел это использование битовых представлений или использование переполнения (по модулю 2^N) в расчётах. Отметим, что также не рекомендуется использовать беззнаковый тип чтобы указать на отсутствие отрицательных значений: в этом случае используйте assert-ы.

Если код возвращает размер контейнера, то убедитесь, что его тип (размера) является достаточным для любого возможного использования. И если сомневаетесь, используйте тип подлиннее.

Будьте внимательны при конвертировании целочисленных типов. Может появится неопределённое поведение (UB), ведущее к багам безопасности и другим проблемам.

Беззнаковые целые числа

Беззнаковые целые числа отлично подходят для работы с битовыми полями и модульной арифметики. Так сложилось, что стандарт C++ использует беззнаковые числа и для возврата размера контейнеров (хотя многие члены организации по стандартизации и считают это ошибкой; в любом случае, сейчас это уже не изменить). Ситуация, что беззнаковая арифметика по поведению является модульной (заворачивание значений при переполнении) и отличается от обычной (знаковой), не позволяет компилятору диагностировать большое количество ошибок. Фактически, такое поведение затрудняет оптимизацию.

С другой стороны, совместное использование беззнаковых и знаковых целых чисел создаёт ещё больше проблем. Лучшее решение: старайтесь использовать итераторы вместо указателей и явных размеров; не мешайте беззнаковые числа вместе со знаковыми; избегайте беззнаковых чисел (кроме работы с битовыми полями и для модульной арифметики); не используйте беззнаковые числа только чтобы показать, что переменная неотрицательная.

Совместимость с 64-бит

Написанный код должен быть совместим как с 64-битной, так и с 32-битной архитектурой. Особое внимание обращайте на печать в консоль, операции сравнения и выравнивание структур.

  • Форматные символы printf() для некоторых целочисленных типов основываются на макроопределениях (например, PRI из <cinttypes>). И такой подход является непрактичным, нежелательным и т.д. Поэтому очень желательно не использовать функцию printf (и подобные) в своём коде, или даже переписать существующий код. Вместо неё можно использовать библиотеки, поддерживающие безопасное форматирование числовых значений, такие как StrCat
    или Substitute
    для простых преобразований, или std::ostream
    .

    К сожалению, макросы PRI являются единственным переносимым способом указать формат для типов данных с задаваемым размером (int64_t, uint64_t, int32_t, uint32_t и т.д.). По возможности не передавайте аргументы таких типов в функции, основанные на printf. Исключение составляют типы, для которых есть свой выделенный модификатор длины, например size_t (z), ptrdiff_t (t) и maxint_t (j).

  • Помните, что sizeof(void*) != sizeof(int). Используйте intptr_t в случае, если требуется целочисленный тип размером, равным размеру указателя.
  • Будьте аккуратны с выравниванием структур, особенно тех что записываются на диск. Обычно классы и структуры, в которых есть член типа int64_t или uint64_t, по умолчанию выравниваются на границу 8 байт в 64-битных системах. Если в коде есть подобные структуры, они сохраняются на диске и используются 32-битным и 64-битным кодом, то обязательно проверьте, что структуры упаковываются одинаково на обеих архитектурах. Большинство компиляторов позволяют задать выравнивание структур. В gcc можно использовать __attribute__((packed)). В MSVC — #pragma pack() и __declspec(align()).
  • Используйте инициализацию в фигурных скобках если требуется создать 64-битную константу. Например:

    int64_t my_value{0x123456789};
    uint64_t my_mask{3ULL << 48};
    

Макросы препроцессора

Избегайте определения макросов, особенно в заголовочных файлах. Вместо этого используйте встраиваемые функции, перечисления или переменные-константы. Если используете макросы, то в имени используйте префикс — название проекта. Не используйте макросы, чтобы переопределить или дополнить C++ API.

Использование макросов подразумевает, что программист видит один код, а компилятор — другой. Это может вызвать неожиданные последствия, особенно если макросы глобальные.

Ситуация может усугубиться, когда макросы используются для переопределения С++ или другого публичного API. При любых ошибках в использовании API потребуется разбираться в логике макросов; увеличивается время разбора кода инструментами рефакторинга или анализаторами. Как результат, использование макросов в таких случаях запрещено. Например, откажитесь от подобного кода:

class WOMBAT_TYPE(Foo) {
  // ...
 public:
  EXPAND_PUBLIC_WOMBAT_API(Foo)
  EXPAND_WOMBAT_COMPARISONS(Foo, ==, <)
};

К счастью, в C++ зависимость от макросов поменьше, чем в C. Вместо макросов для высокопроизводительного кода можно использовать встраиваемые функции. Для хранения констант есть const переменные. Чтобы для удобства «укоротить» длинное имя переменной используйте ссылки. Вместо применения макросов для условной компиляции кода используйте… лучше не используйте условную компиляцию (конечно же это не касается защиты от повторного включения заголовочных файлов через #define). Тем более условная компиляция затрудняет тестирование кода.

С другой стороны, есть приёмы кодирования, которые делаются только через макросы. Обычно это можно увидеть в низко-уровневых библиотеках. Также есть приёмы (преобразование в строку, объединение строк и т.д.), которые нельзя сделать средствами самого языка напрямую. В любом случае, перед использованием макросов попробуйте найти способ реализации без макросов. Если же необходимо использовать макросы для определения интерфейса то предварительно обязательно проконсультируйтесь с руководством.

Следующие правила позволят избежать ряда проблем с макросами. По возможности следуйте им:

  • Не определяйте макросы в заголовочных (.h) файлах.
  • Определяйте (#define) макросы по возможности ближе к месту первого использования. И когда макросы уже не используются, то делайте #undef.
  • Не делайте #undef существующих макросов с последующим их переопределением своей версией. Вместо этого лучше придумайте для своих макросов уникальное имя и используйте его.
  • Постарайтесь не использовать макросы, которые раскрываются в большие и неустойчивые конструкции C++. По крайней мере, документируйте такое поведение.
  • Старайтесь не использовать ## для генерации имени функции/класса/переменной.

Настоятельно не рекомендуется экспортировать макросы из заголовочных файлов (т.е. определять макрос и не делать #undef его в конце заголовочного файла). Если макрос экспортируется из заголовочного файла, то он должен иметь глобальное уникальное имя. Как вариант, добавьте префикс с именем пространства имён проекта (заглавными буквами).

0 и nullptr/NULL

Используйте nullptr для указателей и ‘’ для char-ов (не используйте 0 для этих целей).

Для указателей (адресов) используйте nullptr, это улучшает безопасность типов.

Для проектов C++03 лучше используйте NULL, а не 0. Хотя оба варианта эквивалентны, NULL обычно ассоциируется с указателями. Также некоторые C++ компиляторы могут определять NULL специальным образом и выдавать более адекватные предупреждения компиляции. Никогда не используйте NULL как числовое значение (целочисленное или с плавающей запятой).

Используйте ‘’ в качестве символа конца строки (пустого символа). Это улучшает читабельность кода.

sizeof

Рекомендуется использовать sizeof(переменная) вместоsizeof(тип).

Используйте sizeof(переменная) если необходим размер определённой переменной. sizeof(переменная) будет возвращать корректное значение даже если в дальнейшем изменится тип переменной. sizeof(тип) можно использовать, когда код не работает с конкретной переменной, например в случае форматирования/разбора данных, где соответствующий тип C++ не подходит.

struct data;
memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(Struct)); // Плохо
if (raw_size < sizeof(int)) {
  LOG(ERROR) << "compressed record not big enough for count: " << raw_size;
  return false;
}

Вывод типов

Используйте вывод типов только если это сделает код более читабельным или более безопасным. Не используйте его только из-за неудобства написания полного типа.

Определение
Есть ряд ситуаций, когда типы в C++ коде могут (или даже необходимо) быть выведены компилятором и это более предпочтительно, чем явно их прописывать:

  • вывод типов аргументов шаблонной функции
    • Шаблонная функция может вызываться без указания явных шаблонных типов. Компилятор выводит эти типы из аргументов функции:
      template <typename T>
      void f(T t);
      f(0);  // Вызывается f<int>(0)

  • переменная с auto типом
    • Декларация переменной может использовать auto вместо типа. Компилятор выводит тип из выражения инициализации, следуя правилам, аналогичным для шаблонной функции (во всяком случае, пока не используются фигурные скобки вместо круглых).
      auto a = 42;  // a типа int
      auto& b = a;  // b типа int&
      auto c = b;   // c типа int
      auto d{42};   // d типа int, а не std::initializer_list<int>
      

      auto может использоваться совместно с const, или в составе указателя или ссылки. Однако, auto не может использоваться как аргумент шаблона. Изредка можно увидеть использование decltype(auto) вместо auto и в этом случае выводимый тип является результатом применения decltype
      к переданному выражению инициализации (прим.: и, например, сохранить квалификаторы ссылки).

  • вывод типа возвращаемого значения функции
    • autodecltype(auto)) можно использовать для указания возвращаемого значения функции. Компилятор выводит тип возвращаемого значения из выражения return в теле функции, следуя тем же правилам, что и при объявлении переменной:
      auto f() { return 0; }  // Возвращаемый f тип - int

      Возвращаемый тип лямбды может быть выведен аналогичным способом (хотя это делается при отсутствии возвращаемого типа, а не в случае использования auto). Синтаксис указания возвращаемого типа в конце для функций также использует auto, но это не вывод типа, скорее альтернативный синтаксис для явно указанного возвращаемого типа.

  • общие (generic) лямбды
    • В описании лямбды можно использовать auto в качестве одного или нескольких типов параметров. Как результат, оператор вызова лямбды будет шаблонной функцией вместо обычной, с отдельными параметрами шаблона по одному на каждый auto:
      // Сортируем `vec` по возрастанию
      std::sort(vec.begin(), vec.end(), [](auto lhs, auto rhs) { return lhs > rhs; });

  • инициализация захватываемых переменных лямбды
    • В лямбде в секции захвата можно явно прописать новые переменные, инициализированные значением:
      [x = 42, y = "foo"] { ... }  // тип x - int, y - const char*

      Синтаксис не позволяет указать тип новой переменной, он выводится аналогично auto переменным.

  • вывод аргументов шаблонного класса
    • См. соответствующий раздел.

  • структурная привязка
    • При объявлении кортежей, структур или массивов с использованием auto можно указать имена отдельных элементов вместо имени полного объекта. Эти имена называются «структурная привязка», а декларация — соответственно «декларация структурной привязки». Синтаксис не позволяет задать тип ни полного объекта, ни отдельных имён:
      auto [iter, success] = my_map.insert({key, value});
      if (!success) {
        iter->second = value;
      }

      auto можно использовать с квалификаторами const, & и &&. Отметим, что эти квалификаторы формально применяются к анонимному кортежу/структуре/массиву, а не к отдельным привязкам. Правила определения конечного типа привязок довольно запутанные, однако в большинстве случаев всё довольно логично. Можно только отметить, что тип привязки обычно не может быть ссылочным, даже если в декларации указана ссылка (хотя поведение всё равно может быть как у ссылки).

В приведённом выше описании не указаны многие детали, для дополнительной информации используйте приведённые ссылки.

За

  • Название типов в C++ может быть длинным и громоздким, особенно при использовании шаблонов и пространств имён.
  • Когда название типа C++ повторяется несколько раз внутри небольшого куска кода или декларации, это повторение не улучшает читабельность.
  • В ряде случаев вывод типов безопаснее, т.к. позволяет избежать случайных копирований или преобразований типов.

Против
При явном указании типов код C++ становится более ясным и понятным, особенно если вывод типов опирается на информацию из совершенно другой части кода. В выражении наподобие:

auto foo = x.add_foo(); // Плохо. Что есть foo?
auto i = y.Find(key);

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

Необходимо разбираться, выдаётся ли ссылка при выводе типа, производится ли копирование (особенно если оно не предполагалось).

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

Вердикт
Основное правило: используйте вывод типов только если это сделает код более ясным и безопасным. Не используйте вывод типов только чтобы облегчить написание кода. Не забывайте, что читатели кода могут быть в другой команде и не знакомы с этим проектом. Поэтому, хотя явные типы могут считаться понятным, очевидным и избыточными для одних, они могут содержать полезную информацию для других. Например, можно полагать, что возвращаемый тип make_unique<Foo>() очевиден. Однако, в случае MyWidgetFactory() лучше считать по-другому.

Эти принципы применяются для всех видов вывода типов. Однако, существуют тонкости, описанные ниже.

Вывод аргументов шаблонной функции

Вывод аргументов шаблонной функции это практически всегда хорошо. Это стандартный и ожидаемый способ работы с шаблонными функциями, т.к. вызов такой функции напоминает работу с обычной (но перегруженной для различных аргументов) функцией. И шаблонные функции желательно проектировать так, чтобы вывод аргументов был корректный и безопасный, или функция не должна компилироваться.

Вывод типов локальных переменных

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

std::unique_ptr<WidgetWithBellsAndWhistles> widget_ptr =
    absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2);
absl::flat_hash_map<std::string,
                    std::unique_ptr<WidgetWithBellsAndWhistles>>::const_iterator
    it = my_map_.find(key);
std::array<int, 0> numbers = {4, 8, 15, 16, 23, 42};
auto widget_ptr = absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2);
auto it = my_map_.find(key);
std::array numbers = {4, 8, 15, 16, 23, 42};

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

auto it = my_map_.find(key);
if (it != my_map_.end()) {
  WidgetWithBellsAndWhistles& widget = *it->second;
  // Do stuff with `widget`
}

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

Не используйте decltype(auto) при наличии более простых альтернатив, т.к. результат использования не всегда легко предсказуем.

Вывод типа возвращаемого значения

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

Вывод типов параметров

Параметры с auto в ламбдах должны использовать с осторожностью, т.к. реальный тип определяется кодом лямбды, а не её объявлением. И, конечно, явное указание типа обычно более понятно, за исключением случаев, когда лямбда определяется рядом с местом её использования (можно одновременно видеть и определение лямбды и её вызов) или лямбда передаётся в настолько известный интерфейс, что используемые аргументы очевидны (например, см. вызов std::sort выше).

Инициализация переменных захвата ламбды

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

Структурные привязки

В отличие от других форм вывода типов, структурные привязки могут дать дополнительную информацию за счёт правильного именования элементов полного объекта. Т.е. декларация структурной привязки может улучшить читабельность кода по сравнению с использованием явного типа (даже если auto не рекомендуется). Структурные привязки хорошо подходят при работе с парами или кортежами (см. пример использования insert выше), потому что в последних нет «говорящих» названий полей. С другой стороны, в целом не рекомендуется использовать пары и кортежи, пока внешний API, наподобие insert, явно этого не потребует.

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

auto [/*field_name1=*/ bound_name1, /*field_name2=*/ bound_name2] = ...

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

Вывод аргументов шаблонного класса

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

Определение

Вывод аргументов шаблонного класса (CTAD) проявляется, когда переменная декларируется с типом шаблона, но без указания аргументов (даже без угловых скобок):

std::array a = {1, 2, 3};  // Тип `a`: std::array<int, 3>

Компилятор выводит аргументы из выражения инициализации, используя «гайд» для шаблонов, который может быть явный и неявный.

Явный гайд походит на декларацию функции с возвращаемым типом в конце, только без auto вначале, и имя функции есть имя шаблона. Например, ранее приведённый пример опирается на следующий гайд для std::array:

namespace std {
template <class T, class... U>
array(T, U...) -> std::array<T, 1 + sizeof...(U)>;
}

Конструкторы в основном определении шаблона (т.е. не в специализации) также неявно определяют «гайд».

Когда объявляется переменная, использующая CTAD, компилятор выбирает «гайд» на основе правил выбора (разрешения) перегруженного конструктора, и возвращаемый гайдом тип становится типом переменной.

За
CTAD иногда может уменьшить количество формального кода.

Против
Неявные «гайды», получаемые из конструкторов, могут реализовывать нежелательное или даже неправильное поведение. Эта проблема может часто проявляться для конструкторов, написанных до появления CTAD в C++17, т.к. авторы кода просто не знали о тех проблемах, которые вызовет CTAD. И далее, добавление явных «гайдов» для исправления проблемы может поломать любой существующий код, который использует неявные гайды.

У CTAD есть много недостатков, аналогичных недостаткам auto, т.к. оба механизма выводят (полный или частичный) тип переменной на основе инициализации. CTAD выдаёт больше информации, чем auto, однако всё равно не содержит явного указания, если информация была пропущена.

Вердикт
Не используйте CTAD на шаблонных классах, пока не будет поддержки механизма и обеспечен хотя бы один явный «гайд» (предполагается, что в пространстве имён std всё поддерживается). Желательно, чтобы недопустимое использование CTAD приводило к предупреждениями компилятора.

В любом случае, использование CTAD должно следовать общим правилам при выводе типов.

Лямбды

Используйте лямбды в подходящих случаях. Желательно использовать явный захват переменных, если лямбда будет выполнена вне текущей области видимости.

Определение
Лямбды это лаконичный и удобный способ создания объектов — анонимных функций. Особенно они полезны, когда нужно передавать функцию как аргумент. Например:

std::sort(v.begin(), v.end(), [](int x, int y) {
  return Weight(x) < Weight(y);
});

Лямбды также позволяют захватывать переменный из текущей области видимости либо (явно) по имени, либо (неявно) через захват по-умолчанию. Явный захват предписывает перечислить все требуемые переменные: либо как значения, либо как ссылки:

int weight = 3;
int sum = 0;
// Захват `weight` по значению и `sum` по ссылке.
std::for_each(v.begin(), v.end(), [weight, &sum](int x) {
  sum += weight * x;
});

Захват по-умолчанию применяется ко всем переменным, используемым в теле лямбды, в том числе и к this (если используются члены класса):

const std::vector<int> lookup_table = ...;
std::vector<int> indices = ...;
// Захват `lookup_table` по ссылке, сортировка `indices` по значению
// ассоциированных элементов из `lookup_table`.
std::sort(indices.begin(), indices.end(), [&](int a, int b) {
  return lookup_table[a] < lookup_table[b];
});

Захват переменной может быть также с инициализатором, что можно использовать для перемещения (move) переменных по значению или для других случаев, не подпадающих под обычный захват по значению или ссылке:

std::unique_ptr<Foo> foo = ...;
[foo = std::move(foo)] () {
  ...
}

Такой тип захвата (часто называемый init или generalized) нужен скорее не для собственно «захвата» переменных (или даже имён) из текущей области видимости. Этот синтаксис нужен для определения членов объекта лямбды:

[foo = std::vector<int>({1, 2, 3})] () {
  ...
}

И тип такой переменной с инициализатором выводится согласно правилам, аналогичным использованию auto.

За

  • Лямбды это очень удобный и лаконичный способ определения объектов-функций (например, для передачи в алгоритмы STL), что может улучшить читабельность.
  • Правильное использование захвата по-умолчанию может устранить избыточность и выявить важные исключения при захвате.
  • Лямбды, std::function и std::bind можно использовать совместно как механизм обратного вызова общего назначения. Упрощается написание кода, принимающего/использующего функции как аргументы.

Против

  • Захват переменных может быть источником ошибок с недействительными указателями, особенно если лямбда выходит за текущую область видимости.
  • Захват по умолчанию может вводить в заблуждение, т.к. он не защищает от ошибок недействительных указателей. Захват указателя на объект по значению не приводит к созданию копии самого объекта. При этом часто возникают те же особенности жизненного цикла, что и при захвате по ссылке. Учтите, что ‘this’ также захватывается по значению и это может привести к проблемам, т.к. он часто используется неявно.
  • Захват фактически объявляет новые переменные (вне зависимости от наличия инициализатора), хотя это отличается от обычного синтаксиса C++. В частности, нет указания типа переменной, даже в виде auto (хотя захват с инициализацией может сделать это неявно через приведение типов), и это отличается от типовой декларации.
  • Захват с инициализацией использует вывод типов, и подвержен тем же проблемам, что и auto, плюс сам синтаксис не содержит указания, что происходит вывод типов.
  • В ряде случаев использование лямбд может сильно затруднить понимание кода (очень длинные функции) или потребовать дополнительного контроля.

Вердикт

  • Используйте лямбды в подходящих случаях. Применяйте форматирование, описанное ниже.
  • Рекомендуется явно указывать захват переменных, если лямбда может выйти за текущую область видимости. Например, вместо:
    {
      Foo foo;
      ...
      executor->Schedule([&] { Frobnicate(foo); })
      ...
    }
    // ПЛОХО! При беглом просмотре можно упустить, что лямбда использует
    // ссылку на foo и this (если Frobnicate является членом класса).
    // Если лямбда вызывается после возврата из текущей функции, то
    // это приведёт к проблемам, т.к. foo и другие объекты могут быть
    // уже разрушены.  
    

    лучше написать:

    {
      Foo foo;
      ...
      executor->Schedule([&foo] { Frobnicate(foo); })
      ...
    }
    // ЛУЧШЕ - Компилятор выдаст ошибку, если Frobnicate является методом класса.
    // Также явно указано, что foo захватывается по ссылке.
    

  • Используйте захват по-умолчанию по ссылке ([&]), только если жизненный цикл лямбды явно короче чем у любой переменной.
  • Используйте захват по-умолчанию по значению ([=]), только как средство привязки нескольких переменных для короткой лямбды. Не рекомендуется писать лямбды с объёмным и сложным кодом вместе с захватом по-умолчанию по значению.
  • Используйте захват только существующих переменных из текущей области видимости. Не используйте захват с инициализаторами, чтобы ввести новые имена или подменить существующие. Вместо этого можно объявить обычную новую переменную и передать её в лямбду, или вместо лямбды определить отдельную полноценную функцию.
  • Вопросы по указанию параметров и возвращаемому типу рассмотрены в разделе Вывод типов.

Метапрограммирование на шаблонах

Не используйте сложные/запутанные шаблоны в коде.

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

За
Метапрограммирование позволяет создавать очень гибкие интерфейсы, у которых высокая производительность и отличная типобезопасность. Например, Google Test, std::tuple, std::function и Boost.Spirit были бы невозможны без таких средств.

Против
Техники метапрограммирования часто непонятны всем, кроме экспертов языка. Код, использующий шаблоны запутанными/сложными способами, часто нечитабелен, его сложно отлаживать и поддерживать.

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

Метапрограммирование усложняет проведение рефакторинга, затрудняя работу инструментов. Во-первых, код шаблона раскрывается в различных контекстах и трудно проверить, что в каждом из них код остаётся корректным. Во-вторых, ряд инструментов работает уже с AST (прим.: абстрактное синтаксическое дерево), которое описывает структуру кода только после раскрытия шаблонов. И в этом случае может быть тяжело обнаруженные проблемы отобразить на исходные конструкции в коде и определить, что требуется переписать.

Вердикт
Метапрограммирование в ряде случаев позволяет создать понятные и простые в использовании интерфейсы (которые были бы сложнее в ином случае), однако есть соблазн всё сделать чересчур заумным. Поэтому его лучше использовать в небольшом количестве низкоуровневых компонентов, чтобы сложность поддержки компенсировалась полезностью и широтой применения.

Дважды подумайте перед тем, как использовать метапрограммирование или другие сложные техники на шаблонах: может ли средний программист в команде понять и поддерживать такой код (особенно после переключения с другого проекта); сможет ли не-C++ программист (или другой случайный читатель) понять сообщения об ошибках или отладить выполнение функции, которую он вызывает. Если используются рекурсивное инстанцирование шаблонов, список типов, метафункции, шаблоны выражений или используется SFINAE или трюк с sizeof для разрешения перегрузки функции — скорее всего вы зашли слишком далеко.

Если используется метапрограммирование, то готовьтесь приложить усилия для минимизации сложности, а также её изоляции. По возможности скрывайте код с метапрограммированием внутри реализации, чтобы сделать пользовательские заголовочные файлы более читабельными. Код с метапрограммированием должен быть очень хорошо откомментирован: следует подробно задокументировать, как использовать код и на что будет похож «сгенерированный» результат. Обратите особое внимание на сообщения об ошибках, которые выдаёт компилятор, когда делается что-то ошибочное. Учтите, что сообщения об ошибках являются частью вашего пользовательского интерфейса и код следует доработать так, чтобы сообщения об ошибках были понятными для пользователя и объясняли, что нужно делать для их исправления.

Boost

Используйте только одобренные библиотеки из коллекции Boost.

Определение

Boost это популярная коллекция проверенных, бесплатных и открытых библиотек C++.

За
В целом код Boost является высококачественным, портируемым и во многом дополняется стандартную библиотеку C++, например, в таких областях как свойства типов или улучшенные связыватели (binder).

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

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

  • Call Traits из boost/call_traits.hpp
  • Compressed Pair из boost/compressed_pair.hpp
  • Boost Graph Library (BGL) из boost/graph, за исключением сериализации (adj_list_serialize.hpp) и параллельных/распределённых алгоритмов и структур данных (boost/graph/parallel/* и boost/graph/distributed/*).
  • Property Map из boost/property_map, за исключением параллельных/распределённых (boost/property_map/parallel/*).
  • Iterator из boost/iterator
  • Часть Polygon, которая работает с построением диаграмм Вороного и не зависит от остальной части Polygon: boost/polygon/voronoi_builder.hpp, boost/polygon/voronoi_diagram.hpp, and boost/polygon/voronoi_geometry_type.hpp
  • Bimap из boost/bimap
  • Statistical Distributions and Functions из boost/math/distributions
  • Special Functions из boost/math/special_functions
  • Root Finding Functions из boost/math/tools
  • Multi-index из boost/multi_index
  • Heap из boost/heap
  • flat-контейнеры библиотеки Container: boost/container/flat_map и boost/container/flat_set
  • Intrusive из boost/intrusive
  • Библиотека boost/sort
  • Preprocessor из boost/preprocessor

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

std::hash

Не определяйте специализации std::hash.

Определение

std::hash<T> это объект-функция, который hash контейнеры C++11 используют для получения хэш-ключа от типа T, если явно не указана другая хэш-функция. Например, std::unordered_map<int, std::string> это hash map, используюущая std::hash<int> для расчёта хэша ключа, тогда как std::unordered_map<int, std::string, MyIntHash> использует для расчёта MyIntHash.

std::hash определена для всех целочисленных чисел и с плавающей запятой, указателей, enum-ов, некоторых типов стандартной библиотеки (например, string и unique_ptr). Также допустимо определить специализацию для своих собственных типов.

За

std::hash легко использовать, она упрощает код, т.к. нет необходимости вводить свои именования. Специализация std::hash это стандартный способ определения хэша, удобный и понятный и новичкам и внешним командам.

Против
Хорошую специализацию std::hash может быть сложно написать: она может содержать большое количество формального и одинакового кода. Также это может потребовать усилий и по идентификации входных данных и по созданию самого алгоритма. Автор типа данных обычно ответственен за первое, а со вторым чаще всего возникают проблемы, т.к. это требует определённой экспертизы, которой у автора обычно нет (да она и не нужна). При этом плохая реализация алгоритма может внести уязвимости в безопасность кода, например от атак hash flooding.

Даже для экспертов создание хорошей специализации std::hash может представлять сложность, особенно для составных типов, т.к. хорошая реализация не может просто рекурсивно вызывать std::hash на всех членах данных. Высококачественные хэш-алгоритмы содержат большое количество внутренних состояний, и упаковка их в размер size_t (что функция std::hash и возвращает) обычно является самой медленной частью в вычислениях, так что это должно выполняться не более одного раза.

Именно из-за этого std::hash не работает с std::pair и std::tuple, и в язык не добавлена их поддержка.

Вердикт
Можно использовать std::hash с типами, которые поддерживаются «из коробки», но не добавляйте специализации для своих типов. Если требуется хэш-таблица с ключом, который не поддерживается std::hash, то возможно следует воспользоваться другими (ранними) хэш-контейнерами (например, hash_map); в них используется другой хэш-алгоритм, на который этот запрет не влияет.

Если всё равно требуется стандартный контейнер, то задаёте собственную хэш-функцию для ключа, например:

std::unordered_map<MyKeyType, Value, MyKeyTypeHasher> my_map;

Прежде всего узнайте у автора типа данных о наличии готовой хэш-функции (а вдруг?). В противном случае создавайте свою, в сотрудничестве с автором, или без него.

Возможно, скоро появится хэш-функция, которая сможет работать с любым типом и использовать новый механизм подстройки, при этом у неё не будет недостатков std::hash.

Остальные возможности C++

Некоторые расширения современного C++, также как и Boost, содержат код с плохой читабельностью, с удалённой добавочной информацией о типах, с использованием метапрограммирования. Другие же расширения дублируют существующий функционал, что может привести к путанице и дополнительной конвертации кода.

Вердикт
Настоятельно не рекомендуется использовать следующие возможности C++:

  • Рациональные числа (относящиеся к времени компиляции) (<ratio>), т.к. за интерфейсом может стоять сложный шаблон.
  • Заголовочные файлы <cfenv> и <fenv.h>, т.к. многие компиляторы не поддерживают корректную работу этого функционала.
  • Заголовочный файл <filesystem>, который недостаточно протестирован, и подвержен уязвимостям в безопасности.

Нестандартные расширения

Нестандартные расширения C++ не рекомендуется использовать, пока явно не указано обратное.

Определение
Компиляторы поддерживают различные расширения, не являющиеся частью стандартного C++. Например, __attribute__, внутренние (intrinsic) функции (__builtin_prefetch), назначенные инициализаторы (Foo f = {.field = 3}), ассемблерные вставки, __COUNTER__, __PRETTY_FUNCTION__, составные выражения (foo = ({ int x; Bar(&x); x }), массивы переменной длины, alloca() и «оператор Элвис» a?:b.

За

  • Нестандартные расширения могут предоставить полезные возможности, которых нет в стандартном C++. Например, многим назначенные инициализаторы нравятся больше, чем типовые конструкции C++ (конструкторы).
  • Сделать указания компилятору по оптимизации производительности кода можно только используя расширения.

Против

  • Нестандартные расширения работают не на всех компиляторах, поэтому их использование ухудшает переносимость кода.
  • Даже если расширения поддерживаются всему требуемыми компиляторами, их спецификация может быть не полной и возможны различия в поведении у разных компиляторов.
  • Для понимания кода нужно знать нестандартные расширения в дополнение к языку программирования.

Вердикт
Не используйте нестандартные расширения. Можно использовать портируемые обёртки кода, которые реализованы с использованием нестандартных расширений и и (желательно) содержатся в одном заголовочном файле (portability header).

Псевдонимы/Alias

Публичные псевдонимы предназначены для использования с API и должны быть хорошо документированы.

Определение
Есть несколько способов для создания имён, являющихся псевдонимами для других сущностей:

typedef Foo Bar;
using Bar = Foo;
using other_namespace::Foo;

При написании нового кода рекомендуется использовать using, а не typedef. Это обеспечивает более согласованный синтаксис с остальным C++ кодом и поддерживает работу с шаблонами.

Аналогично другим декларациям, псевдонимы, введённые в заголовочном файле, обычно являются частью публичного API этого файла. Исключения касаются случаев, когда псевдонимы объявлены внутри определения функции, private секции класса или явно отмеченном внутреннем пространстве имён. Такие псевдонимы, а также введённые в .cc файлах являются «деталями реализации» (т.к. клиентский код к ним не обращается) и не подпадают под действие этих правил.

За

  • Псевдонимы могут улучшить читабельность кода, сокращая длинные и сложные имена.
  • Псевдонимы могут уменьшить дублирование: например, сделав именование типа, часто использующегося в API, в дальнейшем это может облегчить изменение этого типа на другой.

Против

  • Псевдонимы, размещённые в заголовочном файле (где клиентский код может обращаться к ним), увеличивают количество сущностей в этом файле API и его сложность.
  • Клиентский код может полагаться на особенности публичных псевдонимов, и это усложняет внесение изменений.
  • Есть соблазн создавать публичные псевдонимы, которые будут использоваться только во внутренней реализации. Такой подход может влиять на сам API и усложнит его поддержку.
  • Псевдонимы увеличивают риск создания дубликата для другого имени.
  • Псевдонимы могут ухудшить читабельность кода, если хорошо известным сущностям будут давать незнакомые имена.
  • Псевдонимы типов могут создать «ненадёжное» соглашение по API: то ли псевдоним всегда будет идентичным указанному типу (API не изменится, тип можно использовать любым способом); то ли тип у псевдонима может поменяться (тогда рекомендуется использовать только небольшую часть возможностей).

Вердикт
Не вводите псевдонимы в публичный API только для облегчения написания кода в реализации; псевдоним должен прежде всего быть полезен для клиентского кода.

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

Не объявляйте публичные псевдонимы на пространства имён в своём API. (См. также Пространство имён).

Например, следующие псевдонимы описывают способ их использования в клиентском коде:

namespace mynamespace {
// Используется для хранения измерений. DataPoint может меняться с Bar* на другой
// внутренний тип, его следует трактовать как абстрактный указатель.
using DataPoint = foo::Bar*;
// Набор измерений. Добавлен для удобства пользователя.
using TimeSeries = std::unordered_set<DataPoint, std::hash<DataPoint>, DataPointComparator>;
}  // namespace mynamespace

Приведённые ниже псевдонимы не документируют способы использования и часть из нихвообще не предназначения для использования в клиентском коде:

namespace mynamespace {
// Плохо: непонятно, как это использовать.
using DataPoint = foo::Bar*;
using std::unordered_set;  // Плохо: это для внутреннего удобства
using std::hash;           // Плохо: это для внутреннего удобства
typedef unordered_set<DataPoint, hash<DataPoint>, DataPointComparator> TimeSeries;
}  // namespace mynamespace

Однако, локальные псевдонимы очень удобны внутри определения функций, private секций классов, внутренних пространств имён и в .cc файлах:

// В .cc файле
using foo::Bar;

Примечания:
Изображение взято из открытого источника.

В новом эпизоде подкаста «Списать не получится» рассказываем, как правильно пользоваться своей ленью и почему не стоит увлекаться кофеином и сладким, заставляя мозг работать

Ваш браузер не поддерживает аудиоплеер.

Подписывайтесь на нас в Apple Podcasts, «Яндекс.Музыке», Spotify, CastBox или на любой другой платформе, где вы нас слушаете. А еще, не забывайте проверять канал «Списать не получится» в Telegram — там мы публикуем больше информации по теме эпизодов, и делимся полезными ссылками.

По разным оценкам, вместимость нашего мозга эквивалентна 2,5 млн Гб цифровой информации. Даже во время отдыха мозг обрабатывает количество информации, равной примерно 100 тыс. слов.

Уже в 2011 году американцы потребляли в пять раз больше контента, чем в 1986-м. Прошло десять лет — и представьте, сколько всего нужно обработать нашему мозгу сегодня. И это при том, что мир постоянно трансформируется — меняется и наша повседневность, подкидывая новые вызовы и задачи, которые требуют решений. Сделать что-то креативное для клиентов на работе, решить с каким подрядчиком работать бизнесу, сделать ребенку костюм зайца на школьный праздник — и это только малая часть того, что ждет нашего внимания в течение дня.

Обычно нам хочется подойти к любому делу творчески — придумать что-то новое, классное и важное, но вдруг в голове возникает ступор: мозг отказывается что-либо придумывать и оставляет нас наедине с банальными решениями (а иногда и вовсе без них). В этот момент мозг, будто компьютер, сталкивается с «перегрузкой». Острота ума снижается, тяжело вспомнить с ходу нужные слова, а новые знания, добытые пару недель назад, просто выветриваются.

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

Таймлайн беседы

00:26 — Почему мы тупим: что такое мозговой туман

5:50 — Избавиться от тумана: как помочь мозгу разгрузиться

7:40 — Что влияет на уровень интеллекта

11:49 — Мгновенная помощь: как заставить мозг работать прямо сейчас

15:49 — Какие повседневные привычки помогают мозгу

21:53 — Пик активности: почему не стоит заставлять мозг работать

24:41 — От чего лучше отказаться, чтобы мозг работал лучше

Хайлайты эпизода

На уровень интеллекта влияют два фактора:

  1. Гены. Чем старше мы становимся, тем сильнее на уровень нашего интеллекта влияет генетическая расположенность.
  2. Среда. Важно создавать условия жизни, которые коррелируют с нашими задатками и позволяют развивать их.

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

Фото:Pexels

Лень — это нормальная реакция нашей психики на задачи, которые кажутся бессмысленными. Не ругайте себя, а воспринимайте такие периоды как возможность отсечь ненужное. Есть два варианта взаимодействия с ленью:

  1. Разобраться, почему именно мы не хотим делать ту или иную задачу. Возможно, она действительно не имеет никакого смысла — тогда попробуйте вообще от нее избавиться.
  2. Принять лень и воспринимать ее как возможность отдохнуть от когнитивного контроля. Состояние блуждания ума и появление сторонних мыслей активируют сеть пассивного режима работы мозга. В такие моменты мы становимся более творческими, выдвигаем новые идеи.

За концентрацию в организме человека отвечает гормон кортизол. Пик его активности — с 8:30 до 10:30 утра, поэтому лучше всего сложные задачи решать в это время. Конечно, не стоит забывать, что все циклы в организме человека очень индивидуальны, так что постарайтесь найти свой «золотой час» и подстроить расписание так, чтобы оно соответствовало вашим особенностям.

Советы о том, как помочь мозгу разгрузиться

Высыпайтесь. Сон — это естественный ресурс организма, который помогает нам восстанавливать силы и перезагружать систему. Из-за недосыпа будет сложно принимать даже простые решения повседневной жизни. Попробуйте уделять сну 7–9 часов.

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

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

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

Общайтесь. Не отключайтесь от социальных активностей — это поднимет настроение, память и познавательные способности.

Составьте расписание. Хаос заставляет мозг паниковать. Распорядок дня — правила, которые разгружают мозг и позволяют ему затрачивать больше энергии на выполнение творческих или рабочих задач. Расписание — привычка, которая дает мозгу сигнал о том, что он в безопасности: чем сложнее устроена ваша жизнь, тем сложнее мозгу сосредоточиться.

Откажитесь от жирной пищи и углеводов. Они провоцируют сонливость, поэтому перед важными мероприятиями и творческими брейнштормами лучше не увлекаться тяжелой пищей.

Пейте больше воды — она улучшает кровообращение. А алкоголя лучше избегать. Он отключает систему торможения в мозге, поэтому мы становимся более свободными. Но на когнитивные способности алкоголь оказывает негативное влияние, потому что разрушает нервные клетки и связи между ними.

https://ria.ru/20211229/omikron-1766067426.html

Назван новый симптом «омикрона»

Британские врачи выявили новый симптом «омикрона» — РИА Новости, 29.12.2021

Назван новый симптом «омикрона»

У пациентов с «омикроном» может появиться «туман в голове», пишет Express со ссылкой на приложение ZOE COVID. РИА Новости, 29.12.2021

2021-12-29T19:00

2021-12-29T19:00

2021-12-29T19:41

распространение коронавируса

великобритания

коронавирус covid-19

/html/head/meta[@name=’og:title’]/@content

/html/head/meta[@name=’og:description’]/@content

https://cdnn21.img.ria.ru/images/07e5/0b/17/1760292389_0:0:3022:1700_1920x0_80_0_0_84ad14d0a329c3d668a76289daa6fd19.jpg

МОСКВА, 29 дек — РИА Новости. У пациентов с «омикроном» может появиться «туман в голове», пишет Express со ссылкой на приложение ZOE COVID.Как отмечается в статье, при заражении этим штаммом SARS-CoV-2,» традиционные» симптомы, такие как кашель, лихорадка и потеря или изменение вкуса и обоняния, могут не появляться. По данным приложения ZOE COVID, примерно у половины зараженных в Лондоне этих недомоганий не было.В то же время у пациентов наблюдаются другие симптомы, один из которых — «мозговой туман» — «вялое, расплывчатое и нечеткое» мышление.»Похоже, что COVID-19 влияет на различные части нашего тела, включая наш мозг и другие системы органов», — пояснил врач Эндрю Бадсон.Он уточнил, что повреждения легких, сердца, почек или других органов и сопутствующие симптомы могут нарушить работу мозга. Предполагается, что омикрон вызывает это состояние с самого начала заражения, а не спустя некоторое время, как при «долгом ковиде».Разработчики ZOE COVID напомнили также о других специфических признаках «омикрона» — потере аппетита, насморке, головной боли, усталости, боли в горле и чихании.Новый штамм B.1.1.529 обнаружили в Ботсване и ЮАР в двадцатых числах ноября. Он содержит десятки мутаций в S-белке, необходимом патогену для заражения клеток. По мнению исследователей, многие из новых изменений в геноме SARS-CoV-2 указывают на высокую трансмиссивность этого варианта и устойчивость к защитным антителам переболевших и привитых, хотя окончательные выводы делать пока рано. Предположительно, B.1.1.529 изначально развился в организме человека с ослабленным иммунитетом, например, зараженного ВИЧ.Всемирная организация здравоохранения признала B.1.1.529 «вызывающим озабоченность» и присвоила ему название «омикрон» — по 15-й букве греческого алфавита. Эксперты считают, что заражаться могут уже переболевшие и вакцинированные, а симптомы варьируются от усталости до головной боли и ломоты в теле.Наиболее эффективной защитой от коронавируса остается вакцина. Министр здравоохранения Михаил Мурашко подчеркивал, что большинство пациентов с COVID-19, которые поступают в российские стационары, не были привиты.

https://ria.ru/20211227/omikron-1765725937.html

https://ria.ru/20211228/immunitet-1765754080.html

великобритания

РИА Новости

internet-group@rian.ru

7 495 645-6601

ФГУП МИА «Россия сегодня»

https://xn--c1acbl2abdlkab1og.xn--p1ai/awards/

2021

РИА Новости

internet-group@rian.ru

7 495 645-6601

ФГУП МИА «Россия сегодня»

https://xn--c1acbl2abdlkab1og.xn--p1ai/awards/

Новости

ru-RU

https://ria.ru/docs/about/copyright.html

https://xn--c1acbl2abdlkab1og.xn--p1ai/

РИА Новости

internet-group@rian.ru

7 495 645-6601

ФГУП МИА «Россия сегодня»

https://xn--c1acbl2abdlkab1og.xn--p1ai/awards/

https://cdnn21.img.ria.ru/images/07e5/0b/17/1760292389_17:0:2748:2048_1920x0_80_0_0_f2172c6d5970d859568c012389924eb1.jpg

РИА Новости

internet-group@rian.ru

7 495 645-6601

ФГУП МИА «Россия сегодня»

https://xn--c1acbl2abdlkab1og.xn--p1ai/awards/

великобритания, коронавирус covid-19

19:00 29.12.2021 (обновлено: 19:41 29.12.2021)

Британские врачи выявили новый симптом «омикрона»

МОСКВА, 29 дек — РИА Новости. У пациентов с «омикроном» может появиться «туман в голове», пишет Express со ссылкой на приложение ZOE COVID.

Как отмечается в статье, при заражении этим штаммом SARS-CoV-2,» традиционные» симптомы, такие как кашель, лихорадка и потеря или изменение вкуса и обоняния, могут не появляться. По данным приложения ZOE COVID, примерно у половины зараженных в Лондоне этих недомоганий не было.

В то же время у пациентов наблюдаются другие симптомы, один из которых — «мозговой туман» — «вялое, расплывчатое и нечеткое» мышление.

«Похоже, что COVID-19 влияет на различные части нашего тела, включая наш мозг и другие системы органов», — пояснил врач Эндрю Бадсон.

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

Разработчики ZOE COVID напомнили также о других специфических признаках «омикрона» — потере аппетита, насморке, головной боли, усталости, боли в горле и чихании.

Новый штамм B.1.1.529 обнаружили в Ботсване и ЮАР в двадцатых числах ноября. Он содержит десятки мутаций в S-белке, необходимом патогену для заражения клеток. По мнению исследователей, многие из новых изменений в геноме SARS-CoV-2 указывают на высокую трансмиссивность этого варианта и устойчивость к защитным антителам переболевших и привитых, хотя окончательные выводы делать пока рано. Предположительно, B.1.1.529 изначально развился в организме человека с ослабленным иммунитетом, например, зараженного ВИЧ.

Всемирная организация здравоохранения признала B.1.1.529 «вызывающим озабоченность» и присвоила ему название «омикрон» — по 15-й букве греческого алфавита. Эксперты считают, что заражаться могут уже переболевшие и вакцинированные, а симптомы варьируются от усталости до головной боли и ломоты в теле.

Наиболее эффективной защитой от коронавируса остается вакцина. Министр здравоохранения Михаил Мурашко подчеркивал, что большинство пациентов с COVID-19, которые поступают в российские стационары, не были привиты.

На Горнерграт (№ 4 на карте 1) мы отправились во второй день пребывания в Церматте. Гора эта относительно невысока, чуть больше 3 км, и с неё открываются прекрасные виды на Маттерхорн и ледник Горнер, второй по величине в Альпах.

Карта 1. Церматт и окружающие его горы. Чёрным цветом показаны подъёмники.

Карта 1. Церматт и окружающие его горы. Чёрным цветом показаны подъёмники.

Плюс наверх можно заехать прямо из Церматта по зубчатой железной дороге, поднимающейся почти на полтора километра, а потом спуститься вниз среди красивейших альпийских пейзажей, не особо напрягаясь. Упустить такую возможность при хорошей погоде было бы просто непростительно.

Карта 2. Гора Горнерграт и её окрестности. Красным пунктиром обозначен наш спуск вниз к станции Riffelalp, где мы сели на поезд и спустились в Церматт.

Карта 2. Гора Горнерграт и её окрестности. Красным пунктиром обозначен наш спуск вниз к станции Riffelalp, где мы сели на поезд и спустились в Церматт.

Гора Горнерграт (3130 м), общий вид со стороны Маттерхорна.

Гора Горнерграт (3130 м), общий вид со стороны Маттерхорна.

Узкоколейная железная дорога из Церматта на вершину Горнерграта длиной 9 км была построена в 1898 году. Она с самого начала была электрифицирована и оборудована зубчатой тягой, поскольку уклон пути составляет в отдельных местах до 20 градусов. На дороге пять промежуточных станций, что удобно для трекинга в районе Горнерграта. Эта дорога — главная «рукотворная» достопримечательность Церматта, поскольку позволяет легко и комфортно полюбоваться пиком Маттерхорна и фантастическими пейзажами с ледниками, спускающимися с окружающих Церматт четырёхтысячников.

Зубчатая железная дорога с поездом на склоне Горнерграта.

1

Зубчатая железная дорога с поездом на склоне Горнерграта.

Одевшись потеплее (поскольку температура наверху градусов на 10 ниже, чем в Церматте), ранним утром мы сели на поезд и отправились наверх. Первое, что бросилось в глаза — стоявший в низинах густой туман и низко висящие облака, частично закрывавшие Маттерхорн.

Вид из окна поезда в начале пути: в низинах лежит туман.

Вид из окна поезда в начале пути: в низинах лежит туман.

Проезжаем станцию «Riffelberg»

Проезжаем станцию «Riffelberg»

Вид из поезда на восточный склон Маттерхорна.

5

Вид из поезда на восточный склон Маттерхорна.

На вершине туман стал ещё гуще, так что видно было очень мало. При этом, правда, вид на ледники был какой-то сюрреалистический.

Туман на вершине Горнерграта.

Туман на вершине Горнерграта.

Ледник в тумане:)

Ледник в тумане:)

Мы набрались терпения и стали ждать, когда туман рассеется. Когда это произошло, мы смогли разглядеть в деталях отель на вершине. Его построили в 1907 году и почему-то оборудовали астрономической обсерваторией с двумя телескопами. Видимо, в те времена светового загрязнения ещё не было, и по ночам в ясную погоду было хорошо видно звездное небо.

Когда тумана стало еще меньше, из-за здания отеля выглянула верхушка Маттерхорна!

Отель с обсерваторией на вершине Горнерграта.

Отель с обсерваторией на вершине Горнерграта.

Из-за отеля показался Маттерхорн.

Из-за отеля показался Маттерхорн.

Верхушка Маттерхорна парит в облаках.

Верхушка Маттерхорна парит в облаках.

Поезд поднимается к вершине Горнерграта и Маттерхорн виден почти весь.

Поезд поднимается к вершине Горнерграта и Маттерхорн виден почти весь.

Когда прояснилось, мы смогли получше разглядеть и саму станцию. Помимо отеля, там есть также ресторан с простой едой, но распложенный на панорамной террасе с видами на ледники. Можно просто выйти на станции, побродить вокруг, полюбоваться видами и вернуться поездом обратно. Большинство туристов так и делает.

Поезд прибыл на конечную станцию - вершину Горнерграта.

Поезд прибыл на конечную станцию — вершину Горнерграта.

Выйдя из поезда, туристы тут же оказываются в окружении фантастических гор и ледников.

Выйдя из поезда, туристы тут же оказываются в окружении фантастических гор и ледников.

Виды с вершины Горнерграта просто поразительны. Ледники так близко, что кажется, их можно потрогать рукой. Маттерхорн виден во всей своей красе, так же как и другие пики, окружающие со всех сторон Церматт. Побывав тут, понимаешь, зачем швейцарцы построили эту дорогу больше 120 лет назад и проникаешься уважением к их предусмотрительности.

Маттерхорн во всей красе.

Маттерхорн во всей красе.

Вид на начало ледника Горнер (Gornergletscher).

Вид на начало ледника Горнер (Gornergletscher).

Место слияния (слегка подтаявшее) двух ледников: Горнера (слева) и Гренца (справа).

Место слияния (слегка подтаявшее) двух ледников: Горнера (слева) и Гренца (справа).

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

Вид на пик Брейтхорн (4184 м) и ледник Теодул. Рядом с ними мы побывали в предыдущий день, поднявшись к «Малому Маттерхорну».

Вид на пик Брейтхорн (4184 м) и ледник Теодул. Рядом с ними мы побывали в предыдущий день, поднявшись к «Малому Маттерхорну».

Длинный язык ледника Горнер.

Длинный язык ледника Горнер.

Фото на память в красивом месте.

Фото на память в красивом месте.

С вершины Горнерграта мы спустились на одну станцию вниз к озеру Riffelsee. Здесь мы собирались сделать открыточный снимок Маттерхорна с его отражением в озере. Однако не получилось из-за облаков и ряби на воде, но всё равно было очень красиво.

Озеро Riffelsee с Маттерхорном на заднем плане.

4

Озеро Riffelsee с Маттерхорном на заднем плане.

Здесь в озере частично отражаются вершины двух четырёхтысячников: Дюфура и Лискамма.

Здесь в озере частично отражаются вершины двух четырёхтысячников: Дюфура и Лискамма.

В швейцарских Альпах невозможно заблудиться - все тропы подписаны. Некую путаницу, правда, вносит указание расстояний во времени ходьбы. У нас рутинно получалось в 2-3 раза больше из-за частых остановок на фотографирование. Лучше бы расстояния указывались в метрах. Это, пожалуй, единственное небольшое пожелание к прекрасной швейцарской организации альпийского туризма.

В швейцарских Альпах невозможно заблудиться — все тропы подписаны. Некую путаницу, правда, вносит указание расстояний во времени ходьбы. У нас рутинно получалось в 2-3 раза больше из-за частых остановок на фотографирование. Лучше бы расстояния указывались в метрах. Это, пожалуй, единственное небольшое пожелание к прекрасной швейцарской организации альпийского туризма.

От этой развилки мы сделали короткий марш-бросок к леднику Горнер (Gornergletscher), который захотелось увидеть ещё ближе. Для этого мы прошли около 2-х километров в направлении Monte Rosa Hutte, небольшого приюта для альпинистов. Ледник Горнер раньше сливался из двух ледников: собственно Горнер (слева на фото ниже) и Гренц (справа, между пиками Дюфур и Лискамм). Сейчас смычка между двумя ледниками растаяла, и поэтому текущий дальше вниз ледник следовало бы переименовать в Гренц, но его по традиции продолжают называть Горнером.

Два ледника: Гренц (справа) сейчас гораздо более «полнолёдный», чем Горнер (слева).

Два ледника: Гренц (справа) сейчас гораздо более «полнолёдный», чем Горнер (слева).

На фоне неземной красоты...

На фоне неземной красоты…

Согласно замерам 2013 года (более поздних данных найти не удалось), ледник Горнер имел в длину 12 км, в ширину — 1,5 км и площадь — 41 кв.км. По этим параметрам он числился вторым по размерам ледником в Альпах, но к настоящему времени наверняка уже стал меньше. Ниже по течению в Горнер впадает еще порядка 5 более мелких ледников. Его талая вода образует речку Matter Vispa, на которой стоит Церматт.

Издалека долго течёт ледник Горнер:)

Издалека долго течёт ледник Горнер:)

Альпийские луга на фоне ледников.

Альпийские луга на фоне ледников.

На обратном пути от ледников впереди вырос Маттерхорн, а правее - небольшой пик Риффельхорн (Riffelhorn).

На обратном пути от ледников впереди вырос Маттерхорн, а правее — небольшой пик Риффельхорн (Riffelhorn).

Сходив к ледникам, мы начали спуск вниз к станции Riffelalp (№ 5 на карте 1). В путеводителях написано, что самые красоты при спуске находятся в пределах трёх станций вниз от Горнерграта, дальше начинается лесистая часть и уже не так впечатляюще красиво. Сама дорога была несложная, без обрывистых скал, но постоянно вниз под гору, так что наибольшая нагрузка пришлась на колени. Расстояние не очень большое, порядка 4-х километров, но шли мы долго из-за постоянных остановок на съёмку.

Отсюда начинается спуск вниз.

Отсюда начинается спуск вниз.

На заднем плане - покрытые ледниками пик Брейтхорн и другие четырёхтысячники.

На заднем плане — покрытые ледниками пик Брейтхорн и другие четырёхтысячники.

Гора Брейтхорн во всей красе.

Гора Брейтхорн во всей красе.

Вышли на пологий склон Горнерграта,

Вышли на пологий склон Горнерграта,

...и оказались на обширных альпийских лугах.

…и оказались на обширных альпийских лугах.

Где-то на этих лугах, над самым обрывом, находится станция Riffelberg. Подойдя к ней, мы увидели красивое зрелище: высоко в небе над станцией летали дельтапланеристы. Потом мы поняли, что они завозят свою технику на поезде, а затем спускаются отсюда вниз в долину. Просто дух захватывает, когда представишь, какие виды им открываются сверху.

В районе ж/д станции Riffelberg.

В районе ж/д станции Riffelberg.

Над станцией и находящейся тут же гостиницей летают дельтапланеристы.

Над станцией и находящейся тут же гостиницей летают дельтапланеристы.

Виды с этих дельтопланов, наверное, потрясающие.

Виды с этих дельтопланов, наверное, потрясающие.

Стоящая особняком часовня хорошо смотрится на фоне гор.

Стоящая особняком часовня хорошо смотрится на фоне гор.

Недалеко от станции мы наткнулись на небольшое стадо валлийских черномордых овец — удивительно забавных домашних животных, обитающих на высокогорьях кантона Вале (отсюда их название). У них длинная густая белая шерсть, закрученные спиралью рога (как штопор:) и заросшие чёрной шерстью морда и нижняя часть ног. Все время, пока мы вокруг них прыгали с фотоаппаратом, они, уткнувшись носом в землю, щипали траву. Так что «личико» своё они нам так и не показали, а жаль — вид бы был очень необычный, мягко говоря (см. такого рода фото, найденное в интернете).

На альпийских лугах пасутся валлийские черномордые овцы.

На альпийских лугах пасутся валлийские черномордые овцы.

Оторвать их от трапезы оказалось невозможным.

Оторвать их от трапезы оказалось невозможным.

Если бы они решились на нас взглянуть, это бы выглядело так (интернет).

Если бы они решились на нас взглянуть, это бы выглядело так (интернет).

На станции Риффельберг находится гостиница Riffelhaus — старейший горный отель Церматта, построенный в 1855 году. Здесь побывали многие знаменитости, в том числе и Марк Твен, известный американский писатель. Он поселился тут в августе 1878 года, поднявшись пешком из Церматта (железной дороги тогда ещё не было), и провел здесь некоторое время, наслаждаясь ежедневными прогулками и написав сатирический рассказ «Восхождение на Риффельберг». В нем Марк Твен издевался над огромной суетой, которая окружала экспедиции 19-го века по исследованию гор вокруг Церматта.

Отель и ресторан Riffelhaus. В августе 1878 года здесь останавливался Марк Твен.

Отель и ресторан Riffelhaus. В августе 1878 года здесь останавливался Марк Твен.

Место роскошное - вокруг потрясающие виды.

Место роскошное — вокруг потрясающие виды.

В честь Марка Твена назвали тропу Riffelberg — Riffelalp. Это — зрелищный маршрут по склону Горнерграта, с которого открывается панорамный вид на множество горных вершин, включая знаменитый Маттерхорн. Также очень хорошо и далеко просматривается долина, в которой расположен Церматт. Мы прошлись по этой тропе и в полной мере оценили её красоту.

На тропе Марка Твена. Внизу виден Церматт.

На тропе Марка Твена. Внизу виден Церматт.

С тропы Марка Твена открывается панорамный вид на долину на многие километры.

6

С тропы Марка Твена открывается панорамный вид на долину на многие километры.

Виды с тропы. Мы приближаемся к нашей цели - отелю и станции Riffelalp.

3

Виды с тропы. Мы приближаемся к нашей цели — отелю и станции Riffelalp.

 Спуск окончен, вышли к часовне отеля Riffelalp.

Спуск окончен, вышли к часовне отеля Riffelalp.

Отель Riffelalp, открыт в 1884 году.

Отель Riffelalp, открыт в 1884 году.

На станции Riffelalp мы сели на поезд и приехали в Церматт. Так завершился насыщенный впечатлениями и прекрасными видами день в Альпах, который мы ещё долго будем вспоминать. Впереди нас ждала Женева, последний город в нашей швейцарской поездке 2018 года.

Теги:
Самостоятельные путешествия, Культурно-познавательный туризм

ДАННОЕ СООБЩЕНИЕ (МАТЕРИАЛ) СОЗДАНО И (ИЛИ) РАСПРОСТРАНЕНО ИНОСТРАННЫМ СРЕДСТВОМ МАССОВОЙ ИНФОРМАЦИИ, ВЫПОЛНЯЮЩИМ ФУНКЦИИ ИНОСТРАННОГО АГЕНТА, И (ИЛИ) РОССИЙСКИМ ЮРИДИЧЕСКИМ ЛИЦОМ, ВЫПОЛНЯЮЩИМ ФУНКЦИИ ИНОСТРАННОГО АГЕНТА.

Если ингушская сторона недовольна соглашением о границе, Чечня с помощью российского руководства добьется решения в свою пользу и вернет себе восемь населенных пунктов из состава Ингушетии, заявил Рамзан Кадыров.

Как сообщал «Кавказский узел», 11 ноября «Чеченавтодор» провел земляные работы на берегу реки Фортанги, по которой проходит граница с Ингушетией. Они отвели речные воды в сторону, что вызвало возмущение в Ингушетии, как среди местных жителей, так и у чиновников. Рамзан Кадыров заявил, что протест против работ организовала в Ингушетии «кучка провокаторов, которые пытаются разыграть националистическую карту». 23 ноября по приказу Кадырова все силовые подразделения Чечни выдвинулись на три дня на границу республики, протяженность которой – 841 километр. Опрошенные «Кавказским узлом» эксперты расценили это как демонстрацию силы.

13 ноября Рамзан Кадыров заявил, что может отнять у Ингушетии часть ее земель. «Земли, которые когда-то [первый президент самопровозглашенной Чеченской Республики Ичкерия Джохар] Дудаев отдал им (Ингушетии) во временное пользование, незаконным путём – я могу их вернуть обратно… Если кто-то будет что-то болтать, то я сниму его штаны и, как флаг, пронесу. Пусть тот, кто недоволен, перейдёт эту Фортангу и выскажет это… Мы можем забрать обратно эти земли. У нас есть на это силы, но, несмотря на это, мы этого не делали», – сказал глава Чечни. За этими словами Кадырова стоит поддержка Кремля, заявили возмущенные ингушские комментаторы в соцсетях.

Рамзан Кадыров на совещании в Грозном, в основном посвященном ситуации с коронавирусом, вновь обратился к ситуации на границе и повторил угрозы об отторжении у Ингушетии территорий за счет пересмотра соглашения между прежним руководством Ингушетии и Дудаевым.

«Если не согласны с тем, что мы сделали, то мы попросим руководство государства, чтобы приняли правильное решение <…> Таким образом, к нам вернутся наши восемь населенных пунктов», – приводят сегодня «Кавказ.Реалии»* перевод слов Кадырова с видеозаписи совещания, опубликованной в ночь на 25 ноября на странице главы Чечни в соцсети «ВКонтакте». «Кавказский узел» проверил и подтверждает точность перевода этой цитаты.

Угрозами отторжения территорий от Ингушетии Рамзан Кадыров добивается от властей соседней республики давления на недовольных ситуацией с границей, указали ранее «Кавказскому узлу» политолог Дмитрий Орешкин, экономист Сергей Жаворонков и кавказовед Ахмет Ярлыкапов. Глава Ингушетии воздержался от публичных комментариев по поводу работ на реке Фортанге, чтобы не обострять ситуацию и не вступать в заведомо проигрышный конфликт с Рамзаном Кадыровым, но такая позиция не добавляет Махмуд-Али Калиматову популярности в республике, считают опрошенные «Кавказским узлом» аналитики.

Рамзан Кадыров на совещании в Грозном 24 ноября 2021 года. Стоп-кадр видео https://vk.com/ramzan?w=wall279938622_612650&z=video279938622_456244722%2Fda6fba03cff8a12078%2Fpl_post_279938622_612650В сопроводительном тексте к видео на странице Кадырова в соцсети «ВКонтакте» нет угроз о возвращении восьми сел, но приводятся обвинения в провокации.

«Мы наблюдаем попытки кучки провокаторов из соседней республики, которые стремятся вбить клин между двумя братскими народами, используя в качестве инструмента тему границ. Я призвал чеченцев и ингушей не поддаваться на провокации и ни в коем случае не опускаться до уровня взаимных оскорблений. Мы были, есть и всегда будем братьями, несмотря на жалкие попытки некоторых проходимцев, которые ставят под сомнение решение Конституционного суда России о законности принятого соглашения между руководством ЧР и РИ», – написал глава Чечни.

Он также заявил, что «вопрос с установлением границ раз и навсегда закрыт». «И он не подлежит пересмотру. Всем пора уже это усвоить. Сложившаяся ситуация полностью соответствует действующему законодательству и нормам шариата. И мы готовы любому это доказать!» – говорится в публикации.

Чеченская делегация 21 ноября выезжала на берег Фортанги, чтобы по шариату решить вопрос о принадлежности приграничных земель, однако представители Ингушетии не явились. Совет тейпов Ингушетии заранее назвал предложенный формат встречи. «Нам предлагают приехать в лес в районе Фортанги, где [спикер парламента Чечни Магомед Даудов] сам предлагает поклясться на Коране и предлагает поклясться нам. Но это не решение по шариату», — заявил Совет тейпов, уточнив, что это «последнее заявление организации» по вопросу границы между двумя республиками. Поездкой к границе с Ингушетией на встречу с представителями ингушских тейпов Магомед Даудов попытался укрепить свой имидж в глазах земляков, указали опрошенные «Кавказским узлом» кавказоведы.

После резких выступлений в адрес Рамзана Кадырова в сети появилась информация о подготовке покушения на сопредседателя общественной организации «Мекх-Кхел» Сараждина Султыгова и других ингушских активистов, а депутат Госдумы Адам Делимханов объявил кровную месть президенту европейской ассоциации ингушей Ибрагиму Льянову, хотя тот и не высказывался по поводу принадлежности земель на границе Чечни и Ингушетии.

Напомним, что соглашение о чечено-ингушской границе, подписанное в сентябре 2018 года, привело к массовым народным протестам в Ингушетии. Активисты обвинили тогдашнего главу Ингушетии Юнуса-Бека Евкурова в передаче части ингушских земель соседней республике. «Кавказский узел» регулярно обновляет хронику «Протесты в Ингушетии: хроника передела границы с Чечней», в которой собрана информация о тех событиях и преследовании противников соглашения.

Новости о территориальных спорах Чечни с соседними регионами «Кавказский узел» собирает на тематической странице «Как Кадыров Чечню укрупняет». Подробнее о новом витке спора о границе говорится в справке «Кавказского узла» «Русло Фортанги: почему злится Кадыров», а о его предыстории – в справке «Спор о чечено-ингушской границе: аргументы сторон».

* издание внесено Минюстом России в список СМИ-иноагентов.

  • Тула город герой как пишется
  • Тузлуков сказка о мечте
  • Туман рассеется или рассеится как правильно пишется
  • Туда сюда обратно как пишется
  • Туган ягымда коз сочинение