




Однозначного ответа нет, поскольку STL - это набор шаблонов. Шаблоны по самой своей природе компилируются только при использовании. Таким образом, вы можете включить весь STL, и если он фактически не используется, размер, добавленный STL, будет равен нулю. Если у вас очень маленькое приложение, которое может использовать множество различных шаблонов с разной специализацией, занимаемая площадь может быть большой.
Во встроенном проекте с лимитом 64 КБ, который я когда-то делал, я даже не мог связать стандартные библиотеки C. Так что это зависит от того, что вам нужно делать
Использование STL увеличит ваш двоичный размер по двум основным причинам:
По умолчанию компилятор обрабатывает код шаблона как встроенный. Следовательно, если вы используете std::list<int> в нескольких разных модулях компиляции, и компилятор встраивает этот код, каждый из них будет иметь свои собственные локальные встроенные определения функций std::list<int> (что, вероятно, не имеет большого значения, поскольку по умолчанию компилятор будет встроить только очень маленькие определения).
Обратите внимание, что (как указывает Мартин в другом месте) множественно определенные символы удаляются всеми современными компоновщиками C++ на больших платформах, как описано в Документация GCC. Таким образом, если компилятор оставляет код шаблона вне очереди, компоновщик удаляет дубликаты.
Из-за самой природы шаблонов C++ std::list<int> потенциально произвольно отличается от std::list<double>. Фактически, стандарт требует, чтобы std::vector<bool> был определен как битовый вектор, поэтому большинство операций там полностью отличается от std::vector<T> по умолчанию.
С этим может справиться только сопровождающий библиотеки. Одно из решений - взять базовую функциональность и удалить ее из шаблона. Превратите его в структуру данных в стиле C с помощью void* повсюду. Тогда интерфейс шаблона, который видят нижестоящие разработчики, представляет собой тонкую оболочку. Это уменьшает количество дублированного кода, поскольку все специализации шаблонов имеют общую основу.
Вы забываете упомянуть, что только те шаблоны или специализации, которые фактически используются, будут скомпилированы в двоичный файл, и встраивание также может уменьшить размер кода (если функция вызывается только один раз, встраивание приводит к меньшему размеру кода, чем встраивание).
Jalf - эти проблемы относятся ко всему программному обеспечению C или C++, а не только к шаблонам. Но это тоже хороший момент.
Ваш внутренний аргумент не работает. Метод помечается как встроенный, если он определен в объявлении класса. Но это все еще зависит от компилятора, действительно ли код встроен или нет. Если это значительно увеличит размер кода, он не будет встроен.
Да, std :: list <int> отличается от std :: list <double>, поэтому у вас будет две разные версии. Но как бы вы реализовали ту же функциональность в собственном коде? Вы реализуете два разных типа и заранее находитесь в таком же положении, как если бы вы использовали шаблоны.
Даже если компилятор решит не встраивать функцию шаблона, вы все равно получите копию в каждой единице компиляции. Если у вас много модулей, использующих std :: list <int>, это много дублированного кода.
Мартин: да, подкладка абсолютно держится. Даже если компилятор решит, что std :: vector <int> :: push_back () должен быть вне очереди, он будет определен в каждой единице компиляции, которая его использует. Таким образом, он все еще дублируется в окончательном двоичном файле, если у вас нет сложного компоновщика.
@Mark Ransom: АБСОЛЮТНО ЛОЖЬ. Да, он дублируется, но во время компоновки все, кроме одного, ДОЛЖНЫ быть удалены, иначе возникнут ошибки компоновщика. Таким образом, если он не встроен, будет только ОДНА копия каждого метода.
@Tom: Да, компоновщик сложный. Это ТРЕБОВАНИЕ для возможности связывания C++. Попробуйте сбросить символы из полученного приложения, вы найдете только ОДНУ версию каждого метода.
Пожалуйста, прочтите мой пост, чтобы объяснить, почему ваши предположения ошибочны.
@Mark - Вы на 100% правы, с GNU ld и GCC. Не все компиляторы и компоновщики делают это (быстрый поиск в Google показывает, что Sun CC на Solaris 9 плохо себя ведет). Вот обсуждение, объясняющее, почему ld не всегда может просто выбросить несколько определений: sourceware.org/ml/binutils/2002-02/msg00142.html
@Tom: Опять же, не совсем правильно. Компилятор sun просто придерживается другой тактики. Вместо того, чтобы помещать определения методов шаблона в модуль компиляции, он использует каталог временного кэша. Таким образом устраняются дубликаты с помощью имен файлов для представления методов.
@Tom: Но дискуссии, которую вы цитируете, уже 6 лет, и она полностью устарела.
@Martin: Если вы можете указать мне что-то авторитетное, описывающее, как соответствующие компоновщики C++ требуются для решения этой проблемы, я вытащу обсуждение встраивания. Я хотел бы знать, что должно произойти, когда тела функций различаются.
Компоновщику не требуется отбрасывать повторяющиеся тела кода, только повторяющиеся определения символов. Линкеры традиционно могли удалять только целые модули. Было бы неплохо протестировать несколько, чтобы узнать, что они делают.
Ответ Дэйва Делани содержит ссылку, описывающую систему «Borland» - экземпляры шаблонов помещаются в общие блоки, которые компоновщик свернет. Интересный подход, вселяет надежду, что компилятор / компоновщик умнее, чем я думал.
@Tom: developer.apple.com/documentation/developertools/gcc-3.3/gcc /…
@Tom: Все компиляторы / компоновщики будут гарантировать, что исполняемый файл действителен, только если все объекты построены с одинаковыми флагами. Это НЕ проблема с шаблоном, это общая проблема, которая затрагивает все части языка. Вот почему IDE создают разные версии (отладка / выпуск) в разных каталогах.
MSVC объединит функции, если сборка идентична. Поэтому list <int> :: push_back и list <void *> :: push_back часто объединяются, поскольку оба копируют 32 бита, не глядя.
@Martin - Я всегда считал правильным создавать единицы компиляции с разными флагами компилятора, при условии, что они бинарно совместимы. Это все еще действительно, если все тела функций определены вне очереди?
@Martin - документация GNU, которую вы (и Дэйв Делани) указали, чтобы указать, что в некоторых системах GNU ld будет нет обрабатывать автоматическое создание экземпляров шаблона. Означает ли это, что явное создание экземпляра - требуется, чтобы избежать множественно определенных символов на этих платформах?
@Tom: Да, проблема в двоичной совместимости. Но какие флаги это меняют. Вот почему в IDE (которые выбирают безопасный курс) каждая цель создается в отдельных каталогах. И все объекты для цели строятся с использованием одних и тех же флагов. Но это не проблема шаблона.
Я предполагаю, что вы имеете в виду след во время выполнения объем памяти и, следовательно, STL контейнеры.
Контейнеры STL эффективны для того, чем они являются ... контейнеры общего назначения. Если вы выбираете между написанием собственного двусвязного списка или использованием std :: list, пожалуйста ... используйте STL. Если вы подумываете о написании очень зависящих от предметной области, битовых контейнеров для каждой из ваших конкретных потребностей, сначала используйте STL, а затем выберите свои сражения, когда весь ваш код будет работать правильно.
Некоторые хорошие практики:
Дело в STL, потому что это все шаблоны, которые он только увеличивает размер, когда вы фактически используете. Если вы не используете метод, этот метод не создается.
Но за то, что вы используете, всегда будет цена.
Но настоящий вопрос вы должны задать. Будет ли размер больше или меньше вашей собственной реализации? Если вы не используете STL, что вы используете? Вы можете написать собственное письмо от руки, но за это придется заплатить. Это не нулевое пространство, он не будет хорошо протестирован, и вы не будете следовать установленным передовым методам.
Так что на самом деле это не раздувает ваш код. Поскольку добавление эквивалентной функциональности каким-либо другим методом добавит столько же кода, это просто не будет так хорошо протестировано.
В другом сообщении говорится, что код шаблона должен быть встроен или иметь несколько определений. Это абсолютно НЕПРАВИЛЬНЫЙ.
Методы отмечены как встроенные. Но компилятор решает, действительно ли метод встроен или нет. Встраивание компилятора достаточно сложное, чтобы быть встроенным только в том случае, если это поможет в используемой стратегии оптимизации.
Если не встроено, то копия метода будет сгенерирована в каждой единице компиляции, которая использует метод. Но ТРЕБОВАНИЕ к компоновщику C++ состоит в том, что он должен удалять ВСЕ, кроме ОДНОЙ копии этих методов, когда приложение связано с исполняемым файлом. Если компилятор не удалил лишние копии, ему пришлось бы сгенерировать ошибку компоновщика нескольких определений.
Это легко показать:
Следующее иллюстрирует это:
#include <vector>
void a(std::vector<int>& l)
{
l.push_back(1);
l.at(0) = 2;
}
#include <vector>
void b(std::vector<int>& l)
{
l.push_back(1);
l.at(0) = 2;
}
#include <vector>
void a(std::vector<int>&);
void b(std::vector<int>&);
int main()
{
std::vector<int> x;
a(x);
b(x);
}
>g++ -c a.cpp
>g++ -c b.cpp
>nm a.o
<removed other stuff>
000000a0 S __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
<removed other stuff>
>nm b.o
<removed other stuff>
000000a0 S __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
<removed other stuff>
>c++filt __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&)
>g++ a.o b.o main.cpp
nm a.out | grep __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
00001700 T __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
Я думаю, что для точного ответа на исходный вопрос нам нужно больше узнать о среде OP. Мой пост - худший случай, ваш пост - самый частый. Впрочем, поучительно и немного пугающе. Если a.cxx скомпилирован -Os, а b.cxx скомпилирован -O3, функции явно различаются.
Гарантирует ли компилятор двоичную совместимость между объектами, скомпилированными с разными флагами? (Я не знаю). Это одна из причин, по которой IDE (через Targets) собирают все объекты -Os в один каталог, а все -O3 - в другой каталог и только связывают вместе объекты, созданные с использованием тех же флагов.
Игнорируя пока обсуждение STL, есть одна неочевидная важная практика для создания статической библиотеки, занимающей мало места. Разделите свою библиотеку на как можно больше разрозненных единиц компиляции. Например, если вы посмотрите на libpthread.a, вы увидите, что каждая функция имеет свой собственный блок компиляции. Многие компоновщики выбрасывают мертвый код на основе целых единиц компиляции, но не более детализированного, чем это. Если я использую только несколько функций из библиотеки pthreads, мой компоновщик внесет только эти определения и ничего больше. С другой стороны, если бы вся библиотека была скомпилирована в один объектный файл, моему компоновщику пришлось бы добавить всю библиотеку как единый «блок».
Это зависит от используемой вами инструментальной цепочки и применяется только в том случае, если вы создаете статические библиотеки. Но я видел очень ощутимую разницу для больших библиотек, построенных неоптимально.
Хотя в нем не рассматриваются шаблоны STL, в документации по GCC есть небольшой раздел, посвященный минимизации раздувания кода при использовании шаблонов-шаблонов.
Ссылка есть http://gcc.gnu.org/onlinedocs/gcc-4.3.2/gcc/Template-Instantiation.html#Template-Instantiation
Специализации STL, основанные на указателях, могут иметь одну и ту же реализацию. Это потому, что (void *) имеет тот же размер, что и (int *) или (foo *). Так что пока:
vector 〈int〉 и vector 〈foo〉 - разные реализации.
vector 〈int *〉 и vector 〈foo *〉 могут иметь большую часть одной и той же реализации. Многие реализации STL делают это для экономии памяти.
Если весь шаблон определен в заголовке, поэтому он полностью определен, компиляторы, такие как g ++, автоматически создадут копию в каждой единице компиляции, используя класс. Как говорили другие, компоновщик удалит несколько определений.
Они также автоматически встраивают методы в определение класса для более высоких уровней оптимизации. Но этим можно управлять с помощью параметров компилятора.
Очень хороший ответ. Если пойти дальше, решением STL может быть даже меньше, если код, написанный для исключения использования STL, на самом деле не используется.