Форвардная декларация базового класса - PullRequest
15 голосов
/ 23 декабря 2008

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

При этом я столкнулся с двумя проблемами:

  1. Форвардное объявление для базовых классов не работает.

    class B;
    
    class A : public B
    {
    
        // ...
    }
    
  2. Форвардное объявление на классах STD не работает.

    namespace std
    {
        class string;
    }
    
    class A
    {
        string aStringToTest;
    }
    

Как мне решить эти проблемы?

Ответы [ 6 ]

25 голосов
/ 23 декабря 2008

Первая проблема, которую вы не можете решить.

Вторая проблема не связана со стандартными библиотечными классами. Это потому, что вы объявляете экземпляр класса как член вашего собственного класса.

Обе проблемы связаны с требованием, чтобы компилятор мог определить общий размер класса по его определению.

Однако компилятор может определить размер указателя на класс, даже если он еще не имеет его полного определения. Таким образом, возможное решение в таких случаях - иметь указатель (или ссылку) на член в классе потребления.

Не так много помощи в случае базового класса, потому что вы не получите отношения «есть».

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

Во-вторых (как указано в комментарии), std::string является typedef для std::basic_string<char>. Таким образом, вам нужно заранее объявить (а затем использовать), что вместо этого к тому времени вещи становятся очень неясными и трудными для чтения, что является еще одним видом затрат. Это действительно того стоит?

7 голосов
/ 24 декабря 2008

Как отвечал ранее Earwicker, вы не можете использовать предварительные объявления ни в одном из этих случаев, так как компилятору необходимо знать размер класса.

Вы можете использовать только предварительное объявление в наборе операций:

  • объявление функций, которые принимают объявленный вперед класс в качестве параметров или возвращают его
  • объявление указателей членов или ссылок на объявленный вперед класс
  • объявление статических переменных типа, объявленного вперед, в определении класса

Вы не можете использовать его для

  • объявляет атрибут члена данного типа (компилятору требуется размер)
  • определить или создать объект типа или удалить его
  • вызов любого статического или метода-члена класса или доступ к любому члену или статическому атрибуту

(я что-нибудь забыл?)

Примите во внимание, что объявление auto_ptr не то же самое, что объявление необработанного указателя, так как экземпляр auto_ptr попытается удалить указатель, когда он выходит из области видимости, и удаление требует полного объявления типа. Если вы используете auto_ptr in для хранения объявленного типа вперед, вам придется предоставить деструктор (даже если он пуст) и определить его после того, как будет объявлено полное объявление класса.

Есть также некоторые другие тонкости. Когда вы объявляете класс вперед, вы сообщаете компилятору, что это будет класс. Это означает, что он не может быть enum или typedef другого типа. Это проблема, с которой вы сталкиваетесь, когда пытаетесь переадресовать объявление std::string, так как это typedef определенного экземпляра шаблона:

typedef basic_string<char> string; // aproximate

Чтобы переслать объявление строки, вам нужно переслать объявление шаблона basic_string, а затем создать typedef. Проблема в том, что стандарт не устанавливает количество параметров, которые принимает шаблон basic_string, он просто утверждает, что если он принимает более одного параметра, остальные параметры должны иметь тип по умолчанию, чтобы приведенное выше выражение компилировалось. Это означает, что не существует стандартного способа объявить шаблон вперед.

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

template <typename T, typename U> class Test; // correct
//template <typename T> class Test; // incorrect even if U has a default type

template <typename T, typename U = int> class Test {
   // ...
};

В конце совет, который дал вам Родди: вперед, объявите столько, сколько сможете, но примите, что некоторые вещи должны быть включены.

2 голосов
/ 23 декабря 2008

Вы слишком стараетесь решить что-то, что на самом деле не является проблемой. Используйте заголовочные файлы, которые вам нужны, и уменьшите - ГДЕ ВОЗМОЖНО - требования к ним. Но не пытайтесь довести это до крайности, потому что у вас ничего не получится.

В некоторых случаях идиома PIMPL может помочь вам, но не здесь.

2 голосов
/ 23 декабря 2008

В обоих случаях компилятору необходимо знать размер типа. Поэтому предварительной декларации будет недостаточно. Базовый класс может добавлять членов или требовать виртуальную таблицу. Член строки должен был бы увеличить размер класса для хранения размера класса строки STL.

Объявление классов STL в прямом направлении часто нежелательно, поскольку реализации обычно включают в себя явные экземпляры шаблонов, ускоряющие компиляцию.

1 голос
/ 04 марта 2009

> Кажется, что предварительное объявление бесполезно для базовых классов и классов stl.

Коррекция ... Форвардное объявление НЕПРАВИЛЬНО для базовых классов и членов объекта. (Это не «бесполезно», это «неприменимо».)

Базовый класс ДОЛЖЕН быть объявлен (не объявлен вперед), когда он объявлен как базовый класс другого класса.

Член объекта ДОЛЖЕН быть объявлен (не объявлен вперед), когда он объявлен другим классом, или как параметр, или как возвращаемое значение. ПРИМЕЧАНИЕ: ссылка или указатель не имеют этого ограничения.

Коррекция ... Прямое объявление классов STL - согласно ISO 14882 - неопределенное поведение. http://www.gotw.ca/gotw/034.htm

1 голос
/ 23 декабря 2008

Для ваших базовых классов вам нужно иметь полное определение типа, а не просто объявление. Заголовки производных типов должны включать #include заголовок для своих базовых классов.

Для классов в пространстве имен std вы должны включить правильный заголовок - в этом случае - и затем выполнить одну из 3 вещей:

  1. Полностью определите тип: std :: string aStringToTest

  2. Поместить объявление об использовании просто этот тип: используя std :: string;

  3. Вставить объявление об использовании для Пространство имен std: использование пространства имен std;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...