Подтвердить что ты не робот

Форвардная декларация базового класса

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

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

  • Вперед декларация по базовым классам не работает.

    class B;
    
    class A : public B
    {
    
        // ...
    }
    
  • Вперед декларацию по классам STD не работает.

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

(я вообще-то забыл?)

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

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

typedef basic_string<char> string; // aproximate

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

Если, с другой стороны, вы хотите переслать объявление нестандартного шаблона (не 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 {
   // ...
};

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

Коррекция... Объявление Forward является INAPPROPRIATE для базовых классов и членов объекта. (Это не "бесполезно", оно "неприменимо".)

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

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

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

Ответ 6

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

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

  • Полностью квалифицируйте тип: std::string aStringToTest

  • Поместите объявление для использования только для этот тип: используя std::string;

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