Можно задаться вопросом, почему C++ настаивает на размещении деталей реализации класса в определении класса. Например, почему нельзя определить Person следующим образом:
namespace std {
class string; // опережающее объявление
} // (некорректно – см. далее)
class Date; // опережающее объявление
class Address; // опережающее объявление
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
};
Если бы такое было возможно, то пользователи класса Person должны были перекомпилировать свои программы только при изменении его интерфейса.
Увы, при реализации этой идеи мы наталкиваемся на две проблемы. Первая: string – это не класс, а typedef (синоним шаблона basic_string
Вторая (и более существенная) неприятность, связанная с опережающим объявлением, состоит в том, что компилятору необходимо знать размер объектов во время компиляции. Рассмотрим пример:
int main()
{
int x; // определяем int
Person p(
...
}
Когда компилятор видит определение x, он понимает, что должен выделить достаточно места (обычно в стеке) для размещения int. Нет проблем: каждый компилятор знает, какова длина int. Встречая определение p, компилятор учитывает, что нужно выделить место для Person, но откуда ему знать, сколько именно места потребуется? Единственный способ получить эту информацию – справиться в определении класса, но если бы в определениях классов можно было опускать детали реализации, как компилятор выяснил бы, сколько памяти необходимо выделить?
Такой вопрос не возникает в языках типа SmallTalk или Java, потому что при определении объекта компиляторы выделяют только память, достаточную для хранения
int main()
{
int x; // определяем int
Person *p; // определяем указатель на Person
...
}
Это вполне законная конструкция на C++, поэтому вы и сами сможете имитировать «сокрытие реализации объекта за указателем». В случае класса Person это можно сделать, например, разделив его на два класса: один – для представления интерфейса, а другой – для его реализации. Если класс, содержащий реализацию, назвать Personlmpl, то Person должен быть написан следующим образом:
#include
// не могут быть объявлены предварительно
#include
class PersonImpl; // опережающее объявление PersonImpl
class Date; // опережающее объявление классов,
class Address; // используемых в интерфейсе Person
class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);