Пространства имен
За редкими исключениями, ваш код должен находиться в пространстве имен.
Хорошее пространство имен содержит:
- Имя библиотеки/проекта
- Путь к заголовочному файлу
Пример
Если у вас есть заголовочный файл, принадлежащий к библиотеке
animalsи лежащий в папкеinclude/animals/mammals/cute/cat.h, то идеальным пространством имен будет:animals::mammals::cute.
Директива using
Не используйте директиву using в глобальном пространстве имен в заголовках. Вместо этого используйте ее внутри пространства имен, объявленного в заголовке (а еще лучше, внутри функций, в которых необходима )
Для этого правила есть исключения, но в общем случае оно должно выполняться.
Безымянные пространства имен
Если функция или класс используется только внутри файла, в котором она объявлена, то хорошим тоном будет поместить ее в безымянное пространство имен, например:
namespace {
auto add(int x, int y) -> int { return x + y; }
}
auto main() {
return ::add(0, 0);
}Вызов этой функции в дальнейшем будет осуществляться с помощью оператора ::.
Подробнее об этом: Stackoverflow
Предупреждение
Не используйте эту практику в заголовочных файлах.
Прочие замечания
- Не используйте
inline namespace. - Не объявляйте ничего в пространстве имен
std::.
Статические функции
Не нужно помещать статические функции в класс, если они не привязаны логически и семантически к этому классу; не нужно создавать класс только для того, чтобы сгруппировать статические функции. Правильной альтернативой этому будет помещать свободные функции в пространство имен.
Пример:
/* 😡 плохо */
class Math {
static auto add(int x, int y) -> int;
static auto subtract(int x, int y) -> int;
};
/* 😊 хорошо */
namespace math {
[[nodiscard]] auto add(int x, int y) -> int;
[[nodiscard]] auto subtract(int x, int y) -> int;
}Примечание
Исключением может являться экспорт группы функций для QML, однако в таком случае лучшим решением будет создание класса-обертки для свободных функций.
Локальные переменные
Помещайте локальные переменные настолько в обособленной области видимости, насколько это возможно. Соблюдайте принцип RAII (Wikipedia). Никогда не объявляйте переменные без инициализации.
Используйте ключевые слова auto и auto const. Они запрещают объявление неинициализированных переменных.
Пример 1
/* 😡 плохо */
int i;
i = 1;
/* 😊 хорошо */
auto const i = 1; Пример 2
/* 😡 плохо */
std::vector<int> a;
a.push_back(1);
a.push_back(2);
/* 😊 хорошо */
auto const a = std::vector<int>{1, 2};Пример 3
/* 😡 плохо */
int* a = static_cast<int*>(malloc(1024));
if(a != nullptr)
return a;
return make_unexpected("error allocating 1024 bytes");
/* 😊 хорошо */
// область видимости `a` - условие if и его ветви.
if(auto a = static_cast<int*>(malloc(1024)); a != nullptr)
return a;
return make_unexpected("error allocating 1024 bytes");Статические и локальные для потока переменные
Статические переменные должны всегда иметь тривиальный деструктор. Почти всегда из этого правила следует то, что статические переменные должны быть constexpr.
Общие паттерны для статических данных
Статические строки
using namespace std::string_view_literals;
constexpr auto PROJECT_NAME = "meow"sv;Статические контейнеры (std::map, std::set, etc.)
Запрещается использовать стандартные контейнеры в сочетании с ключевым слово static, так как они имеют нетривиальный деструктор.
В качестве альтернативы предлагается использовать std::array фиксированного размера, либо пересмотр архитектуры своего кода.
По той же причине умные указатели не должны использоваться в сочетании с ключевым словом static.
Свои типы данных
Для прочих типов данных в статической памяти выполняется 2 правила:
- Наличие
constexpr-конструктора - Наличие тривиального деструктора
Локальные для потока переменные
Ключевое слово thread_local должно использоваться только в сочетании с ключевым словом constinit.
constinit thread_local auto cat = /* ... */;