Опережающее объявление класса

      Комментарии к записи Опережающее объявление класса отключены

В этой теме 1 ответ, 2 участника, последнее обновление  Васильев Владимир Сергеевич 9 мес., 3 нед. назад.

  • Автор
    Сообщения
  • #3041

    questioner
    Участник

    В исходном коде к статье «Работа с сетью в Qt. Паттерн Адаптер» вместо #include <QTcpServer> используется запись class QTcpServer; в заголовочном файле (.h), однако в соответствующем файле реализации (.cpp) потом все равно содержится директива #include. Зачем это делается?

        # include <QObject>
        # include <QList>
        class QTcpServer;
        class ISocketAdapter;
        class Server : public QObject {
          Q_OBJECT
        public:
          explicit Server(int nPort, QObject *parent = 0);
        public slots:
          void on_newConnection();
          void on_disconnected();
          void on_message(QString);
        protected:
          QTcpServer* m_ptcpServer;
          QList<ISocketAdapter*> m_clients;
        };

    В этом же примере почему то не пишется class QList и т.п., почему?

  • #3042

    Прием, используемый в этом примере называется предварительным (опережающим) объявлением класса (forward reference class). Такое объявление помогает решить, как минимум, две проблемы:

    1 Ослабить зависимости между файлами, сократить время компиляции

    Директива #include говорит компилятору, что он должен включить код одного файла в другой файл. Это сложная операция, которая может занимать много времени. С другой стороны, допустим, мы изменили что-либо в классе — при следующей компиляции проекта будут перекомпилированы все файлы, в которые включался "server.h". В больших проектах такая компиляция может заниматься несколько часов.
    В ряде случаев компиляции всех файлов, использующих изменяемый класс можно избежать. Посмотрите на класс Server — он содержит указатель на класс QTcpServer, т.е. вне зависимости от того, как реализован QTcpServer, наш класс будет содержать, скажем, 4 байта с адресом объекта. Во всех таких случаях мы можем (нам следует) не включать заголовочный файл целиком, а лишь указать компилятору, что такой класс есть в нашей программе при помощи опережающего объявления. Это же относится к списку указателей — QList<ISocketAdapter*>, а также к всем случаям, когда класс содержит (или его функция принимает в качестве аргументов) указатель или ссылку на объект.
    В связи с этим, Скотт Мэйерс рекомендует избегать использования объектов, если можно обойтись ссылками или указателями. Однако, надо помнить, что использование объектов в классе, в отличии от указателей, безопасно с точки зрения исключений — поэтому чтобы ослабить зависимости и при этом не получить проблем с исключениями — используйте какой-либо вид умных указателей.

    2 Разорвать циклические зависимости классов

    Допустим, у нас получилась следующая ситуация:

    // файл foo.h
    #include "bar.h"
    class Foo {
      Bar bar;
    };
    
    // файл bar.h
    #include "foo.h"
    class Bar {
      f(Foo foo);
    };

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

    // файл foo.h
    #include "bar.h"
    class Foo {
      Bar bar;
    };
    
    // файл bar.h
    class Foo;
    class Bar {
      f(const Foo &foo);
    }

    Рекомендую прочитать подробнее про указатели и ссылки — ведь с их помощью можно не только ослабить зависимости, но и повысить гибкость. Кроме того, использование объектов-членов в классах нарушает принцип инверсии зависимостей (DIP — Dependency Inversion Principle).

Для ответа в этой теме необходимо авторизоваться.