Pull to refresh

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'ы в своих проектах.

Вы хотите сказать, что нижестоящие хедеры ожидают того, что перед ними будет стоять windows.h?

#include <Windows.h>	//< переопределяет min/max
#include <vector>	//< написан так, чтобы учесть все 
			//  возможные переопределенные макросы?
#include "myHeader.h"	//< тоже должен учитывать? Да ну нафиг...


Просто я в своих проектах принципиально не использую precompiled headers, какие у них преимущества?

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

Нет, не ожидают, но:
1) define-преобразования будут хотя бы одинаковы для всех единиц трансляции
2) Сам windows.h написан так, чтобы быть всегда первым, MS его в шаблонах проектов в stdafx.h кладёт

Насколько понял позицию Гугла: если вам так нужно windows.h включить до парного заголовочника, то почему бы его не включить в сам парный заголовочник. В нём (парном заголовочнике) используются типы из windows.h или GetCurrentTimeW? — тогда там ему и место!
Я даже не знаю, что хуже — включить Windows.h в хедер или написать в хедере «using namespace std»…
Даже не знаю, что вам предложить: либо не упоминать типы windows в хедере (и тогда его (хедер) можно вставлять первым) или … не упоминать windows в хедере. (Или забить на гугловый кодстайл).

С другой стороны: смотрю на реальный код (Windows, MFC, сгенерённое студией) и в хидерах кастомных компонентов есть инклюды afx…h (в том смысле, что не далеко ушло от windows.h). Как бы и в чём проблема?
Или Вы правы, а я чересчур хочу понять Гугл.

С третьей стороны: они (Гугл) не совсем корпорация …! В исходном документе (ссылка в статье приведена) есть раздел Windows Code. Может имеет смысл глянуть?
Я следую такому правилу: если мне дают задачу на проекте, где уже Windows.h включен, то выход один — смириться. Но в таких проектах также и порядок включений может отличаться от предпочитаемого мной — тут, само собой, я буду писать код в соответствии с их кодстайлом. В своих проектах, где я потенциально расчитываю в будущем портировать это на другие платформы, я соблюдаю гигиену — я могу сдублировать typedef'ы из Windows.h, а порой даже (если нужна именно структура оттуда) — в своем заголовочном файле вместо него ввести сырой массив такого же размера, приправленного static_assert(sizeof() == sizeof()) внутри cpp-файла.
Пример:
// 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-преобразования были идентичными везде.

Можно undef сделать, если такие проблемы возникают. В одной замечательной ОС, с которой пришлось работать, были задефайнены Send и Receive, приходилось их раздефайнить и использовать «настоящие» имена функций, __send и __receive. Ну и более лучше задуматься об абстрагировании от конкретной ОС, тогда подобные проблемы возникнут не более 1 раза в модуле, реализующем эту абстракцию.

Да в общем-то нет проблем. Заменил препроцессор часть токенов — ну флаг ему в руки, гоняться за ним желания нет. windows.h достаточно большой файл.

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

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

По какой причине pragma once в стандарт не принимают?
UFO just landed and posted this here

Мои глаза. Это ж машинный перевод, какого-то другого ресурса.

UFO just landed and posted this here

А смысл? Эффект барабары стрейзанд не остановить. Да и правовые поля неясные — придется помимо технологий еще и кучу юристов обеспечивать.

Всё, что там написанно про forward declaration — это такой бред, что сложно даже поверить, что это писали в Google.

Sign up to leave a comment.

Articles