Включение заголовочных файлов / Форвардная декларация - PullRequest
12 голосов
/ 14 мая 2010

В моем проекте C ++, когда мне нужно использовать включение (#include "myclass.h") заголовочных файлов?И когда мне нужно использовать предварительное объявление класса (class CMyClass;)?

Ответы [ 5 ]

20 голосов
/ 14 мая 2010

Как правило, сначала попробуйте предварительную декларацию. Это уменьшит время компиляции и т. Д. Если это не компилируется, перейдите на #include. Вы должны пойти на #include, если вам нужно выполнить одно из следующих действий:

  1. Доступ к члену или функции класса.
  2. Использовать арифметику указателей.
  3. Используйте sizeof.
  4. Любая информация RTTI.
  5. new / delete, копия и т. Д.
  6. Используйте его по значению.
  7. Наследуй от него.
  8. Иметь его в качестве члена.
  9. Экземпляр в функции.

(6,7,8,9 от @Mooing Duck)

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

11 голосов
/ 13 января 2012

Существует несколько проблем для пересылки декларации:

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

    include "MyProject/MyWidgets/MyFooWidgets/FooUtil/Foo.h"
    

    вместо предварительной декларации

     class Foo ;  
    

    другим легко найти, где объявлен класс Foo. С предварительным заявлением, это не так очевидно; некоторые IDE, такие как Eclipse, могут открывать предварительное объявление, когда пользователь пытается открыть объявление переменной.

  • При связывании может произойти сбой с неопределенными символьными ошибками, когда вы включаете в код файл заголовка, который содержит прямое объявление, но фактическое определение кода находится в какой-то другой библиотеке, с которой вы не связались. Эту проблему удобнее обнаруживать во время компиляции с ошибками типа "Could not find file MyProject/MyWidgets/MyFooWidgets/FooUtil/Foo.h", так как тогда вы будете знать, где искать соответствующий Foo.cpp и определять библиотеку, в которой он содержится.

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

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

Пример: * * тысяча двадцать-пять

class Foo ;

typedef Foo* FooPtr ;
typedef Foo& FooRef ;

class Foo
{
   public:
      Foo( ) ;
      ~Foo( ) ;
}

OR

class TreeNode ;

class Tree
{
private:
   TreeNode m_root ;
}

class TreeNode
{
   void* m_data ;
} ;
11 голосов
/ 14 мая 2010

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

2 голосов
/ 14 мая 2010

Вы должны стремиться к минимизации ваших #include s, чтобы сократить время компиляции, а также чтобы помочь с модульностью и тестируемостью. Как говорит @ypnos, форварды классов превосходны, когда вам нужны только указатели.

Некоторые практические советы о том, как уменьшить зависимости заголовка, см., Например. эта статья .

2 голосов
/ 14 мая 2010

Как новичок, вы всегда должны #include заголовочные файлы, когда вам нужно использовать типы или функции, которые они содержат, - не пытайтесь «оптимизировать» вашу сборку путем прямого объявления вещей - это вряд ли когда-либо необходимо, даже в больших проектах при условии, что проект хорошо спроектирован.

Единственный раз, когда вам абсолютно необходимо предварительное объявление, в таких ситуациях:

struct A {
   void f( B b );
};

struct B {
   void f( A a );
};

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

struct B;   // forward declaration

struct A {
   void f( B b );
};

struct B {
   void f( A a );
};
...