C ++, удаляя # include <vector>или # include <string>в заголовке класса - PullRequest
4 голосов
/ 28 мая 2009

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

Я надеялся, что смогу сделать что-то вроде:

namespace std {
    template <class T>
    class vector;
}

И, объявите вектор в заголовке и включите его в исходный файл.

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

Ответы [ 12 ]

14 голосов
/ 28 мая 2009

Вы не можете безопасно пересылать объявления шаблонов STL, по крайней мере, если вы хотите сделать это переносимым и безопасным. Стандарт четко определяет минимальные требования для каждого элемента STL, но оставляет место для расширений реализации, которые могут добавлять дополнительные параметры шаблона , если они имеют значения по умолчанию . То есть: стандарт гласит, что std :: vector является шаблоном, который принимает как минимум 2 параметра (тип и распределитель), но может иметь любое количество дополнительных аргументов в стандартной совместимой реализации.

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

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

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

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

11 голосов
/ 28 мая 2009

За очень немногими исключениями, вы не можете добавлять вещи в std :; Пространство имен. Поэтому для таких классов, как vector и string, у вас нет другого выбора, кроме как #include соответствующие стандартные заголовочные файлы.

Также обратите внимание, что string - это не класс, а typedef для basic_string<char>.

6 голосов
/ 28 мая 2009

Это не поможет для вектора или строки, но, возможно, стоит упомянуть, что для iostream существует заголовок прямой ссылки, называемый iosfwd.

4 голосов
/ 28 мая 2009

Нет простого очевидного способа сделать это (как другие очень хорошо объяснили).

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

Если вас беспокоит скорость компиляции, я рекомендую вам вместо этого использовать предварительно скомпилированный заголовок и поместить в него эти заголовки std (среди прочего). Это значительно увеличит вашу скорость компиляции.

Извините за ответ "настоящий победитель - тот, кто избегает боя".

4 голосов
/ 28 мая 2009

Я тоже пытался сделать это раньше, но это невозможно из-за шаблонов.

Пожалуйста, смотрите мой вопрос: Форвардная декларация базового класса

Поскольку такие заголовки не меняются в процессе разработки, в любом случае не стоит оптимизировать это ...

4 голосов
/ 28 мая 2009

Стандартные контейнеры часто имеют дополнительные параметры шаблона по умолчанию (распределители и т. Д.), Поэтому это не будет работать. Например, вот фрагмент из реализации GNU:


  template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
    { ... };
3 голосов
/ 28 мая 2009

Просто включите заголовок в любой файл, где вы ссылаетесь на коллекцию STL.

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

Если модули компиляции не создают экземпляры классов, это не сделает ваши объектные файлы больше.

3 голосов
/ 28 мая 2009

Если строка и вектор используются только в подписях непубличных членов вашего класса, вы можете использовать идиому PImpl:

// MyClass.h
class MyClassImpl;
class MyClass{
public:
    MyClass();
    void MyMethod();
private:
    MyClassImpl* m_impl;
};

// MyClassImpl.h
#include <vector>
#include <string>
#include <MyClass.h>
class MyClassImpl{
public:
    MyClassImpl();
    void MyMethod();
protected:
    std::vector<std::string> StdMethod();
};

// MyClass.cpp
#include <MyClass.h>
#include <MyClassImpl.h>

void MyClass::MyMethod(){
    m_impl->MyMethod();
}

Вы всегда включаете вектор и строку в заголовочный файл, но только в часть реализации вашего класса; файлы, включающие только MyClass.h, не будут вытягивать строку и вектор.

2 голосов
/ 28 мая 2009

ПРЕДУПРЕЖДЕНИЕ

Ожидайте, что это вызовет шум.

Язык позволяет вам создавать собственные классы:

// MyKludges.h
#include <vector>
#include <string>
class KludgeIntVector : public std::vector<int> { 
  // ... 
};
class KludgeDoubleVector : public std::vector<double> {
  // ...
};

class KludgeString : public std::string {
  // ...
};

Измените ваши функции, чтобы они возвращали KludgeString и KludgeIntVector. Поскольку это больше не шаблоны, вы можете объявить их в заголовочных файлах и включить MyKludges.h в свои файлы реализации.

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


// LotsOfFunctions.h
// Look, no includes!  All forward declared!
class KludgeString;
// 10,000 functions that use neither strings nor vectors
// ...
void someFunction(KludgeString &);
// ... 
// Another 10,000 functions that use neither strings nor vectors

// someFunction.cpp
// Implement someFunction in its own compilation unit
// <string> and <vector> arrive on the next line
#include "MyKludges.h"
#include "LotsOfFunctions.h"
void someFunction(KludgeString &k) { k.clear(); }
1 голос
/ 28 мая 2009

Полагаю, ваша цель - ускорить время компиляции? В противном случае я не уверен, почему вы хотите удалить их.

Другой подход (не симпатичный, но практичный) заключается в использовании макросохранителей вокруг самого include.

, например

#ifndef STDLIB_STRING
#include <string>
#define STDLIB_STRING
#endif

Хотя это выглядит грязно, на больших кодовых базах это действительно увеличивает время компиляции. Мы создали макрос Visual Studio, который автоматически генерирует охрану. Мы привязываем макрос к ключу для легкого кодирования. Тогда это просто становится стандартом кодирования (или привычкой) компании.

Мы также делаем это для наших собственных целей.

1012 *, например *

#ifndef UTILITY_DATE_TIME_H
#include "Utility/DateTime.h"
#endif

Поскольку у нас есть помощники Visual Studio для автоматической генерации охранников при создании наших собственных заголовочных файлов, нам не нужен #define. Макрос знает, что это внутреннее включение, потому что мы всегда используем

#include ""

формат для наших собственных включений и

#include <>

для внешних включает.

Я знаю, что это не выглядит красиво, но оно ускорило время компиляции на большой базе кода более чем на 1/2 часа (из памяти).

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