Абсолютно. Модель сборки C/C++ - это... гм... анахронизм (если не сказать больше). Для крупных проектов это становится серьезной PITA.
Как правильно заметил Нейл, это не должно быть подходом по умолчанию для дизайна вашего класса, не пытайтесь изо всех сил, если вам это действительно не нужно.
Отказ от включаемых ссылок — это одна из причин, по которой вы должны использовать предварительные объявления.
// 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"
// ...
Это настолько распространено, что это шаблон — шаблон PIMPL. а>. Его использование двоякое: во-первых, он обеспечивает настоящую изоляцию интерфейса/реализации, а во-вторых, уменьшает зависимости сборки. На практике я бы оценил их полезность 50:50.
Вам нужна ссылка в заголовке, у вас не может быть прямого экземпляра зависимого типа. Это ограничивает случаи, когда могут применяться предварительные объявления. Если вы делаете это явно, обычно используется служебный класс (например, boost::scoped_ptr).
Стоит ли время сборки? Определенно, я бы сказал. В худшем случае время сборки полиномиально пропорционально количеству файлов в проекте. другие методы, такие как более быстрые машины и параллельные сборки, могут обеспечить только процентный выигрыш.
Чем быстрее сборка, тем чаще разработчики тестируют то, что они сделали, тем чаще запускаются модульные тесты, тем быстрее можно обнаружить сбои в сборке, и тем реже разработчики заканчивают тем, что прокрастинируют.
На практике управление временем сборки, хотя и необходимо для большого проекта (скажем, сотни исходных файлов), все же дает «удобство» для небольших проектов. Кроме того, добавление улучшений постфактум часто требует терпения, поскольку одно исправление может сократить 40-минутную сборку всего на несколько секунд (или меньше).
person
peterchen
schedule
26.04.2009