Рассмотрим, что случится, если с помощью #include
включить заголовочный файл для А
, или, что более реально, заголовочный файл для полудюжины или более классов, присутствующих в реальном заголовочном файле. Тогда файл реализации (
Создайте предварительное объявление класса, и эти зависимости компиляции исчезнут. Использование предварительного объявления просто создает имя, на которое можно ссылаться далее в заголовочном файле. Компоновщик должен будет сам найти в файлах реализаций подходящее определение.
К несчастью, использовать предварительное объявление можно не всегда. Класс В
в примере 2.4 использует только указатели или ссылки на A
, так что ему достаточно только предварительного объявления. Однако если бы в определении класса В
я использовал функцию-член (метод) или переменную А
или если бы создавал объект типа А
, а не только указатель или ссылку на него, то предварительного объявления окажется недостаточно. Причиной этого является то, что файлы, включающие В
, и если A
является членом В
, то компилятор, чтобы определить размер В
, должен знать размер А
. Указатель или ссылка на что-либо всегда имеют один и тот же размер, так что в случае использования указателей или ссылок подробности об А
компилятор не интересуют, и, следовательно, заголовочный файл не требуется
Неудивительно, что если включить в A
, то потребуется включить через #includ
e заголовок для А
. Это требуется для того, чтобы компилятор мог проверить сигнатуру используемой функции-члена А
или тип переменной-члена А
. Вот иллюстрация кода, требующего #include
.
#include "a.h"
class B {
public:
void f(const A& a) {
foo_ = a.getVal(); // требуется знать, допустимо ли a.getVal
}
}
// ...
В общем случае используйте предварительное объявление тогда, когда это позволяет снизить количество #include
, что отражается на времени компиляции.
2.4. Предотвращение конфликта имен с помощью пространств имен
В несвязанных между собой модулях обнаружены конфликтующие имена или требуется заранее избежать возможности таких конфликтов, создав логические группы кода.
Для структурирования кода используйте пространства имен. С помощью пространств имен можно объединять большие группы кода, находящиеся в разных файлах, в единое пространство имен. Для разбиения больших модулей на подмодули можно использовать вложенные пространства имен, и потребители вашего модуля смогут выборочно открывать элементы вашего пространства имен, которые им требуются. Пример 2.5 показывает несколько способов использования пространства имен.
// Devices.h
#ifndef DEVICES_H__
#define DEVICES_H__
#include
#include
namespace hardware {
class Device {
public:
Device(): uptime_(0), status_("unknown") {}
unsigned long getUptime() const;
std::string getStatus() const;
void reset();
private:
unsigned long uptime_;
std::string status_;
};
class DeviceMgr {
public:
void getDeviceIds(std::list
Device getDevice(const std::string& id) const;
// Other stuff...
};
}
#endif // DEVICES_H__
// Devices.cpp
#include "Devices.h"
#include
#include
namespace hardware {
using std::string;
using std::list;
unsigned long Device::getUptime() const {
return(uptime__);
}
string Device::getStatus() const {
return(status_);
}
void DeviceMgr::getDeviceIds(list
Device DeviceMgr::getDevice(const string& id) const {
Device d;
return(d);
}
}
// DeviceWidget.h
#ifndef DEVICEWIDGET_H__ #define DEVICEWIDGET_H__
#include "Devices.h"
namespace ui {