C++, удаление #include‹vector› или #include‹string› в заголовке класса

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

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

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

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

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


person bias    schedule 28.05.2009    source источник
comment
Мне любопытно, какова ваша цель здесь - почему вы пытаетесь удалить эти заголовки?   -  person Michael Kohne    schedule 28.05.2009
comment
Может быть, в легаси 1000 функций объявлены в одном заголовочном файле, и каждая функция реализована в своем собственном файле реализации. Теперь он хочет добавить функцию 1001, которая будет принимать ссылку const std::string. Если он помещает ‹string› в заголовочный файл, он замедляет компиляцию 1000 исходных файлов, которые не используют std::string. Если он заранее объявляет std::string или использует альтернативу, которая не нравится суетливым, он не оказывает никакого влияния на унаследованное время компиляции и, возможно, избегает необходимости оправдывать использование строки вместо указателя char.   -  person Thomas L Holaday    schedule 28.05.2009
comment
Да, уточните причину. Если речь идет о производительности сборки, см., например. Ответ Джема.   -  person peterchen    schedule 28.05.2009
comment
Причина, по которой он это делает, не имеет никакого отношения к тому, возможно ли это.   -  person Thomas L Holaday    schedule 28.05.2009
comment
Но знание этого может предложить лучшее решение, чем то, которое предлагает он.   -  person    schedule 28.05.2009
comment
Но правильный ответ на вопрос, возможно ли это, не исключает продолжения вопроса о том, считаете ли вы это мудрым или полезным. Вы можете избежать включения стандартных заголовков, предварительно объявив свои собственные производные версии, но это необычная практика, и многие предполагают, что должен быть другой способ, который дает как краткий ответ на вопрос, так и подталкивание к тому, что, по вашему мнению, может быть лучшим решением.   -  person Thomas L Holaday    schedule 28.05.2009
comment
Я беспокоился как о производительности сборки, так и о желании свести к минимуму «скрытые» включения для людей, использующих класс. Хотя, как указал dribeas, им придется включить их в случае, если я не...   -  person bias    schedule 28.05.2009


Ответы (12)


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

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

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

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

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

person David Rodríguez - dribeas    schedule 28.05.2009
comment
Старый ответ, я знаю, но я думаю, что комитет по стандартам пришел к единому мнению, что для реализации недопустимо добавлять дополнительные параметры. Они могут делать такие вещи, как: template <typename T, ...> class vector : private _VectorBase<T, ..., ExtraStuff> { ... }; чтобы получить тот же эффект. - person GManNickG; 02.10.2010

За очень немногими исключениями вам не разрешено добавлять что-либо в std:; пространство имен. Таким образом, для таких классов, как вектор и строка, у вас нет другого выбора, кроме как включить соответствующие стандартные заголовочные файлы #include.

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

person Community    schedule 28.05.2009
comment
В std::vector отсутствует параметр шаблона распределителя, который требуется стандартом. - person Motti; 28.05.2009
comment
У вас нет выбора со вкусом, кроме как включить стандартные заголовочные файлы, но есть и неприятные варианты. - person Thomas L Holaday; 28.05.2009
comment
Предварительное объявление ничего не добавляет в пространство имен std, а только сообщает компилятору о том, что уже есть. Кроме того, правило не является столь строгим: вам разрешено добавлять специализации шаблонов для конкретных типов внутри пространства имен std, если они соответствуют стандарту. Программа может добавить специализации шаблона для любого шаблона стандартной библиотеки в пространство имен std. Это приводит к неопределенному поведению, если объявление не зависит от определяемого пользователем имени внешней связи и если специализация не соответствует требованиям исходного шаблона. - person David Rodríguez - dribeas; 28.05.2009
comment
Честно говоря, мне было непонятно, что пользователь пытался сделать со своим примером кода. Он определенно не пытался добавить новую специализацию шаблона, поэтому я не думаю, что ваша цитата применима. - person ; 28.05.2009
comment
@dribeas: стандарт говорит, что вы не можете добавлять объявления в пространство имен std (кроме допустимых специализаций шаблонов). Если вы не включили какие-либо стандартные заголовки, пространство имен std будет пустым. Объявление добавляется к пространству имен, а не объявляет то, что уже существует. Тем не менее, он будет работать на большинстве компиляторов. Так же как и добавление определений в пространство имен std. - person Steve Jessop; 28.05.2009
comment
Интересный момент «один за другим» (из-за отсутствия лучшего способа обращения к вам). На самом деле возникает вопрос: «добавляет» ли предварительное объявление что-то в пространство имен? Я не верю. Вы сообщаете компилятору, что где-то существует предварительно объявленный символ. Конечно, вы можете считать, что вы внедряете что-то в ранее пустое (для компилятора в этой единице компиляции) пространство имен. - person David Rodríguez - dribeas; 29.05.2009

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

person Fred Larson    schedule 28.05.2009
comment
iosfwd — восхитительная демонстрация мастерства опережающего объявления. - person Thomas L Holaday; 28.05.2009

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


  template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
    { ... };
person Nikolai Fetissov    schedule 28.05.2009
comment
+1, даже если пример плохой. Эта реализация является в точности стандартным определением (минимальным требованием) для вектора. Идея, однако, здравая: стандарт явно позволяет разработчикам добавлять дополнительные аргументы шаблона, если они имеют значение по умолчанию. - person David Rodríguez - dribeas; 28.05.2009

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

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

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

person Tamara Wijsman    schedule 28.05.2009
comment
Ваш вопрос и ответ имели важное различие. Вы намеревались создать экземпляр std::string. В вопросе, который вы разместили, вы даже не могли использовать перенаправленный класс. Здесь вопрос касается ситуации, когда вы можете предварительно объявить обычный класс, но не можете использовать STL из-за его специфических требований. - person David Rodríguez - dribeas; 28.05.2009

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

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

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

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

person Jem    schedule 28.05.2009
comment
Спасибо за совет по скорости компиляции. - person bias; 28.05.2009

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

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

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

person JohnMcG    schedule 28.05.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, не будут извлекать строку и вектор.

person Paolo Tedesco    schedule 28.05.2009
comment
Это решает конкретный сценарий, но не позволяет использовать многие другие. Все ваши объекты должны создаваться динамически, поэтому теперь вы ограничиваете использование элементов, выделенных в стеке. Вы должны создать виртуальный деструктор, который создаст виртуальную таблицу, даже если ваш класс этого не требует. Потребуется огромное количество шаблонного кода только для того, чтобы определить весь интерфейс в базовом классе, переопределить и реализовать его в производных классах, и все эти методы должны быть виртуальными. Использование PIMPL является немного лучшим решением проблемы (нет проблем с включением ‹string› и ‹vector›). - person David Rodríguez - dribeas; 28.05.2009
comment
Да, вы правы насчет использования PImpl. Я обновлю пост. - person Paolo Tedesco; 28.05.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(); }
person Thomas L Holaday    schedule 28.05.2009
comment
Это позволяет вам делать это, но, конечно, не поощряет вас к этому. - person ; 28.05.2009
comment
язык позволяет, но здравый смысл предостерегает от этого. - person xtofl; 28.05.2009
comment
Это ничуть не помогает ОП. Он пытается удалить #include вектора и строки из заголовка. Если он подклассифицирует их, а затем использует их, ему все равно придется включать заголовки. Также, как правило, плохая идея создавать подклассы для большей части STL - на самом деле он не был предназначен для этого, и вы, как правило, создаете больше проблем, чем решаете. - person Michael Kohne; 28.05.2009
comment
Вы никогда не должны наследовать от класса, который не был разработан, чтобы быть производным от него. Это открывает дверь для многих (тонких и не очень) ошибок. - person David Rodríguez - dribeas; 28.05.2009
comment
Он спросил, возможно ли это, а не разумно ли это. - person Thomas L Holaday; 28.05.2009
comment
@Michael Kohne, ему решительно не нужно включать вектор и строку в заголовки, объявляющие его функции, которые возвращают векторы и строки. Там он может объявить свои кладжи. - person Thomas L Holaday; 28.05.2009
comment
Там я добавил яркое предупреждение для Neil Butterworth, xtofl и dribeas и пример кода для Michael Kohne. - person Thomas L Holaday; 28.05.2009
comment
@dribeas, в руководстве по Apache C++ есть примеры извлечения из стандартных контейнеров. stdcxx.apache.org/doc/stdlibug/16-2.html - person Thomas L Holaday; 28.05.2009
comment
Конечно, самый известный продукт Apache написан не на C++. Я не понимаю, почему вы считаете, что они являются признанным авторитетом в области передовой практики C++. - person ; 28.05.2009
comment
Лучшая практика! = существующая практика. Я не понимаю, почему вы думаете, что стандартные контейнеры не предназначены для использования в качестве базовых классов. - person Thomas L Holaday; 28.05.2009
comment
Потому что у них нет виртуальных методов и, следовательно, они не могут обрабатываться полиморфно. И часто существующая практика == плохая практика. - person ; 28.05.2009
comment
Мотивом для получения из родового класса является специализация, а не полиморфизм. - person Thomas L Holaday; 28.05.2009

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

person xtofl    schedule 28.05.2009

За исключением добавления перегрузок в std::swap (единственное исключение, которое я могу сейчас придумать), обычно вам не разрешается добавлять что-либо в пространство имен std. Даже если бы это было разрешено, фактическое объявление для std::vector намного сложнее, чем код в OP. См. ответ Николая Н. Фетисова для примера .

Помимо всего этого, у вас есть дополнительная проблема, связанная с тем, что пользователи вашего класса будут делать с функциями, которые возвращают std::vector или std::string. В разделе 3.10 стандарта C++ говорится что функции, возвращающие такие объекты, возвращают rvalues, а rvalues ​​должен быть полного типа. В английском языке, если ваши пользователи хотят что-то делать с этими функциями, им в любом случае придется #include <vector> или <string>. Я думаю, было бы проще #include заголовки для них в файле .h и покончить с этим.

person Michael Kristofik    schedule 28.05.2009
comment
Можно избавить тех пользователей его класса, которые используют только методы, не имеющие ни векторов, ни строк, от включения ‹vector› или ‹string›. - person Thomas L Holaday; 28.05.2009

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

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

e.g.

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

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

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

e.g.

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

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

#include ""

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

#include <>

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

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

person Shane Powell    schedule 28.05.2009