Стоит ли заранее объявлять библиотечные классы?

StackOverflow https://stackoverflow.com/questions/791108

  •  16-09-2019
  •  | 
  •  

Вопрос

Я только начал изучать Qt, используя их учебник.В настоящее время я работаю над уроком 7, где мы создали новый класс LCDRange.Реализация LCDRange (cpp-файл) использует класс Qt QSlider, поэтому в cpp-файле

#include <QSlider>

но в заголовке есть прямое объявление:

class QSlider;

В соответствии с Qt,

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

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

Стоит ли это делать?Кажется, это имеет смысл, но это еще одна вещь, за которой нужно следить - я чувствую, что было бы намного проще просто включить все в заголовочный файл.

Это было полезно?

Решение

Абсолютно.Модель сборки C / C ++ - это ... кхм...анахронизм (мягко говоря).Для крупных проектов это становится серьезным блюдом.

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

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

// a.h
#include "b.h"
struct A { B * a;  }

// b.h
#include "a.h"  // circlular include reference 
struct B { A * a;  }

// Solution: break circular reference by forward delcaration of B or A

Сокращение времени восстановления - Представьте себе следующий код

// foo.h
#include <qslider>
class Foo
{
   QSlider * someSlider;
}

теперь каждый cpp-файл, который прямо или косвенно загружается в Foo.h, также загружается в QSlider.h и все его зависимости.Это могут быть сотни cpp-файлов!(Предварительно скомпилированные заголовки помогают немного, а иногда и сильно, но они уменьшают нагрузку на диск / процессор в объеме памяти / диска и, таким образом, вскоре достигают "следующего" предела)

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

Сокращение времени инкрементной сборки - Эффект еще более выражен, когда имеешь дело с вашими собственными (а не со стабильной библиотекой) заголовками.Представьте, что у вас есть

// bar.h
#include "foo.h"
class Bar 
{
   Foo * kungFoo;
   // ...
}

Теперь, если большинству ваших .cpp нужно использовать bar.h, они также косвенно используют foo.h.Таким образом, каждое изменение foo.h запускает сборку всех этих cpp-файлов (которым, возможно, даже не нужно знать Foo!).Если bar.h использует вместо этого прямое объявление для Foo, зависимость от foo.h ограничена bar.cpp:

// bar.h
class Foo;
class Bar 
{
   Foo * kungFoo;
   // ...
}

// bar.cpp
#include "bar.h"
#include "foo.h"
// ...

Это настолько распространено, что является закономерностью - в Узор из пупырышек.Его польза двоякая:во-первых, это обеспечивает истинную изоляцию интерфейса / реализации, во-вторых, уменьшает зависимости сборки.На практике я бы оценил их полезность 50:50.

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

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

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

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

Другие советы

Я пользуюсь им постоянно.Мое правило таково, что если ему не нужен заголовок, то я размещаю предварительное объявление ("используйте заголовки, если необходимо, используйте прямые объявления, если можете").Единственное, что отстойно, это то, что мне нужно знать, как был объявлен класс (struct / class, возможно, если это шаблон, мне нужны его параметры, ...).Но в подавляющем большинстве случаев это просто сводится к "class Slider;" или что-то в этом роде.Если что-то требует дополнительных хлопот для простого объявления, всегда можно объявить специальный заголовок forward declare, как это делает Стандарт с iosfwd слишком.

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

Это приблизительный план:

/* --- --- --- Y.hpp */
class X;
class Y {
    X *x;
};

/* --- --- --- Y.cpp */
#include <x.hpp>
#include <y.hpp>

...

Существуют интеллектуальные указатели, которые специально разработаны для работы с указателями на неполные типы.Одним из очень хорошо известных является boost::shared_ptr.

Да, это действительно помогает.Еще одна вещь, которую следует добавить в свой репертуар, - это предварительно скомпилированные заголовки, если вы беспокоитесь о времени компиляции.

Посмотри вверх Часто ЗАДАВАЕМЫЕ ВОПРОСЫ 39.12 и 39.13

Стандартная библиотека делает это для некоторых классов iostream в стандартном заголовке <iosfwd>.Однако это не общеприменимый метод - обратите внимание, что для других стандартных типов библиотек таких заголовков нет, и это не должно (ИМХО) быть вашим подходом по умолчанию к разработке иерархий классов.

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

Существует ОГРОМНАЯ разница во времени компиляции для более крупных проектов, даже с тщательно управляемыми зависимостями.Вам лучше приобрести привычку пересылать объявления и максимально избегать заголовочных файлов, потому что во многих магазинах программного обеспечения, использующих C ++, это требуется.Причина, по которой вы не видите всего этого в стандартных файлах заголовков, заключается в том, что в них интенсивно используются шаблоны, после чего прямое объявление становится затруднительным.Для MSVC вы можете использовать /P, чтобы взглянуть на то, как выглядит предварительно обработанный файл перед фактической компиляцией.Если вы не делали никакого предварительного объявления в своем проекте, вероятно, было бы интересно посмотреть, сколько дополнительной обработки необходимо выполнить.

В общем, нет.

Раньше я пересылал декларировать столько, сколько мог, но не больше.

Что касается Qt, вы можете заметить, что существует <QtGui> включите файл, который будет содержать все виджеты графического интерфейса.Кроме того, существует <QtCore>, <QtWebKit>, <QtNetwork> и т.д.Для каждого модуля есть заголовочный файл.Похоже, команда Qt также считает, что это предпочтительный метод.Они говорят об этом в документации к своему модулю.

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

Когда ты пишешь ...

включить "foo.h"

...таким образом, вы инструктируете обычную систему сборки: "Каждый раз, когда в файле библиотеки foo.h происходят какие-либо изменения, удалите этот модуль компиляции и перестройте его, даже если все это произошло с foo.h было добавлением комментария или добавлением комментария к некоторому файлу, который foo.h включает;даже если все, что произошло, было вызвано тем, что какой-то сверхбрезгливый коллега перенастроил фигурные скобки;даже если ничего не произошло, кроме того, что коллега, находящийся под давлением, проверил foo.h без изменений и непреднамеренно изменил свою временную метку ".

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top