Comments 21
- Порядок #include: сперва объявляю парный *.h (если это *.cpp), потом прочие зависимости из проекта (все, что в двойных кавычках) и только после этого — стандарные заголовки (все, что в <>). Причины тут две:
- Системные заголовки типа Windows.h грешат засильем макросов, тихо подменяя имена внутри включенных заголовков, выбрасывая противные ошибки компиляции/линковки. Пример — макросы min/max.
- Как раз ради самодостаточности заголовочного файла. Опять же, #include <vector> будет незаметно включен во все другие заголовочные файлы, объявленные ниже, что не есть хорошо.
- Наоборот, использование forward declaration везде, где возможно (исключение — если заголовочный файл гарантированно не может своими включениями создать перекрестный include). Опять же, лечить перекрестные зависимости — тот еще геморрой.
Если в коде заменить #include на предварительное объявление для структур B и D, то test() будет вызывать f(void*).
Надеяться, что компилятор узнает о наследуемости без include и использование void* в таком месте — как мне кажется, ССЗБ. Я понимаю, когда void* используют исключительно с POD-типами — я обычно такое заворачиваю в шаблонный метод с std::enable_if_t<std::is_trivially_copyable_v<T>> или добавляю static_assert (предпочитаю этот вариант, ибо можно выкинуть человекопонятное сообщение об ошибке компиляции). Ведь так можно пойти дальше и случайно вызвать delete на таком void* и совершить прочие Undefined Behavior непотрёбства. Вообще, использование void* в плюсах — это как носить заряженный пистолет в кармане. Если уж носите, то не кладите в этот карман ничего другого (перегрузки функций) или купите ему кобуру (обложите static_assert-ами и прочее).
Структура кода, допускающая предварительное объявление (и, далее, использование указателей в качестве членов класса) может сделать код запутанным и медленным
Как правило, решение использовать указатель/ссылку/непостредственно тип при объявлении поля в первую очередь диктуется архитектурными соображениями, аргумент не засчитанПредварительное объявление символов из std:: может вызвать неопределённое поведение.
Опять же, основная причина использования forward declaration — избежание перекрестных зависимостей. В случае со стандартной библиотекой этого можно не бояться и смело писать #include<string>.
Насчет inline-функций, как я понял, вообще лучше не использовать без необходимости (необходимостью считаю случаи, когда необходимо написать тело в пределах того же заголовочного файла, но по причине того, что взаимноконвертируемые типы ссылаются друг на друга, тело методов нужно вынести за пределы определение класса ниже объявления зависимого). Вроде как при включенной оптимизации и параметра генерации кода на этапе линковки линковщик сам разберется и заинлайнит все что надо — так я понял из ответов на SO, но все же некоторая неуверенность присутствует… Лично мне не нравится, когда внутренности геттеров торчат в заголовочном файле (в своем пет-проекте я во все функции добавляю макрос с ассертом для проверки текущего потока по маске, геттеры — не исключение). Помимо этого правила, ко всем вышеперечисленным я пришел опытным путем множественного наступания на грабли. Хотелось бы услышать стороннее мнение на этот счет.
Именно windows.h приходится включать первым, ещё до парного заголовочного файла. Проблема в том, что, например, вызов GetCurrentTime после windows.h превращается в GetCurrentTimeW. Обычно windows.h уходит в precompiled header, так что особых проблем это не вызывает. Интересно, использует (разрешает) ли Гугл precompiled header'ы в своих проектах.
#include <Windows.h> //< переопределяет min/max
#include <vector> //< написан так, чтобы учесть все
// возможные переопределенные макросы?
#include "myHeader.h" //< тоже должен учитывать? Да ну нафиг...
Просто я в своих проектах принципиально не использую precompiled headers, какие у них преимущества?
В зависимости от ситуации могут ускорить компиляцию в несколько раз. Как пример, в одном из моих предыдущих проектов перенос всех заголовочных файлов Qt в PCH привёл к снижению времени компиляции с более получаса до менее 10 минут.
Нет, не ожидают, но:
1) define-преобразования будут хотя бы одинаковы для всех единиц трансляции
2) Сам windows.h написан так, чтобы быть всегда первым, MS его в шаблонах проектов в stdafx.h кладёт
С другой стороны: смотрю на реальный код (Windows, MFC, сгенерённое студией) и в хидерах кастомных компонентов есть инклюды afx…h (в том смысле, что не далеко ушло от windows.h). Как бы и в чём проблема?
Или Вы правы, а я чересчур хочу понять Гугл.
С третьей стороны: они (Гугл) не совсем корпорация …! В исходном документе (ссылка в статье приведена) есть раздел Windows Code. Может имеет смысл глянуть?
// lock.h
// Копирую typedef из Windows.h, но не включаю его
typedef struct _RTL_CRITICAL_SECTION CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
class Sync
{
public:
// В x64 и x86 структура имеет разный размер
template<int = sizeof( size_t )>
struct ArchHelper;
template<>
struct ArchHelper<4>
{
// 32-bits operations
constexpr static size_t CRITICAL_SECTION_SIZE = 24U;
};
template<>
struct ArchHelper<8>
{
// 64-bits operations
constexpr static size_t CRITICAL_SECTION_SIZE = 40U;
};
constexpr static size_t CRITICAL_SECTION_SIZE = ArchHelper<>::CRITICAL_SECTION_SIZE;
//...
CRITICAL_SECTION* GetCriticalSection() const;
private:
// Сырой массив
mutable unsigned char m_criticalSectionBuffer[ CRITICAL_SECTION_SIZE ];
//...
};
// Lock.cpp
#include "Threading/Lock.h"
#include <windows.h> //< Включаю последним
//...
CRITICAL_SECTION* Sync::GetCriticalSection() const
{
static_assert( sizeof( m_criticalSectionBuffer ) == sizeof( CRITICAL_SECTION ), "invalid CRITICAL_SECTION buffer size" );
return reinterpret_cast<CRITICAL_SECTION*>( &m_criticalSectionBuffer[0] );
}
//...
например, вызов GetCurrentTime после windows.h превращается в GetCurrentTimeWА вы прямо в бизнес-, так сказать, логике вызываете GetCurrentTime и прочую виндовую самодеятельность или в одном отдельном обособленном модуле, реализующем прослойку между тем, что вам действительно надо (получить текущее время) и вызовом подобных ос-зависимых системных функций?
Этот GetCurrentTime вообще моя личная функция, заведённая в личном классе. Но define'у на это всё равно. Нельзя быть уверенным, что ни один из тысяч define'ов не совпадет ни с кем из сотен имён в том же stl. Проще подключить всегда и первым, чтобы define-преобразования были идентичными везде.
незаметно включен во все другие заголовочные файлы, объявленные ниже, что не есть хорошо.
так #include включает содержимое файлов в текущий файл, а не наоборот. И проблема возникнет только когда в реализации не найдется системный инклуд в другом файле. Добавить недостающее на порядок тривиальнее на мой взгляд.
Всё, что там написанно про forward declaration — это такой бред, что сложно даже поверить, что это писали в Google.
Руководство Google по стилю в C++. Часть 2