LINUX.ORG.RU

hpp vs cpp

 ,


0

2

Привет! Вот вброс. какие есть за и против писать все в заголовочных файлах, с шаблонами и без, и писать, использую *.cpp файлы. (ну я хочу поднять эту тему у себя, вот готовлюсь.)

если это тут возможно, изменю это сообщение и добавлю, ответы. пока что так

hpp подход

  • + ускоряет компиляцию
  • + дает возможность компилятору проверять код
  • + не нужна система сборки

cpp подход

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

Все вообще не так.

hpp (отказ от раздельной компиляции) замедляет компиляцию, остальное вообще мимо кассы.

Насчёт системы сборки - если речь о том что один cpp и остальное хидеры, то она ненужна… если в опциях не запутаетесь.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

замедляет компиляцию

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

если в опциях не запутаетесь

я там выше привел пример скрипта для сборки. мне вот приходится смореть на вывод ninja чтобы понять какие опции туда cmake вставила, в таком варианте будет понятнее.

zerhud
() автор топика
Ответ на: комментарий от AntonI

остальное вообще мимо кассы

что мимо кассы? там только про доступность всего ast при компиляции. это делает возможным всякие compile time проверки для которых, в противном случае, не было бы нужной информации.

zerhud
() автор топика
Ответ на: комментарий от zerhud

что мимо кассы? там только про доступность всего ast при компиляции. это делает возможным всякие compile time проверки для которых, в противном случае, не было бы нужной информации.

Ты привел только пример с выходом за границы. И то это не всегда возможно, я бы даже сказал: возможно очень изредка. Давай кокретный пример и обсудим, полезны ли там предупреждения или нет.

С другой стороны ты что предлагаешь при использовании твоим проектов 5-10 других либ, тоже их каждый раз собирать? Давай какую нибудь Qt так хранить в виде хедеров. Вот забава будет это пересобирать каждый раз.

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

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

но в целом: доступность всего ast дает больше информации. ты можешь ее использовать явно (requries, static_assert, if constexpr, consteval и прочее) и компилятор тоже может ее использовать.

zerhud
() автор топика
Ответ на: комментарий от zerhud

но в целом: доступность всего ast дает больше информации. ты можешь ее использовать явно (requries, static_assert, if constexpr, consteval и прочее) и компилятор тоже может ее использовать.

Это все теория. Может не может… Ты можешь привести реальный пример?

rumgot ★★★★★
()
Ответ на: комментарий от zerhud

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

Может быть и так и эдак конечно. Но чаще и в среднем, если руки прямые, ПЕРЕКОМПИЛЯЦИЯ при разбиение на несколько единиц трансляции ускоряется. Потому что перекомпилировать надо не все а какой то кусок.

что мимо кассы? там только про доступность всего ast при компиляции. это делает возможным всякие compile time проверки для которых, в противном случае, не было бы нужной информации.

Все мимо кассы. Эти оптимизации актуальны для инлайн-функций, ну дык они и так в хидерах. Но есть ещё много неинлайн функций, и там общий анализ кода ничего не даст.

Опять таки, инлайн не всегда хорошо с тз производительности.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

hpp (отказ от раздельной компиляции) замедляет компиляцию

Да?

% hyperfine --runs 10 --prepare 'make clean' 'make partialbin'
Benchmark 1: make partialbin
  Time (mean ± σ):      1.889 s ±  0.024 s    [User: 1.707 s, System: 0.154 s]
  Range (min … max):    1.860 s …  1.933 s    10 runs
 
% hyperfine --runs 10 --prepare 'make clean' 'make wholebin'
Benchmark 1: make wholebin
  Time (mean ± σ):      1.052 s ±  0.017 s    [User: 0.948 s, System: 0.090 s]
  Range (min … max):    1.033 s …  1.075 s    10 runs

Makefile:

partialbin: partialmain.o partialfoo.o
	g++ -o $@ partialmain.o partialfoo.o

partialmain.o: main.cpp
	g++ -o $@ -c -I. $<

partialfoo.o: foo.cpp
	g++ -o $@ -c -I. $<

wholebin: both.cpp
	g++ -o $@ both.cpp

both.cpp: foo.cpp main.cpp
	cat foo.cpp main.cpp > both.cpp
	sed -i '/foo.h/d' both.cpp

clean:
	$(RM) *.o partialbin wholebin both.cpp
// foo.h
#pragma once

void foo();

// foo.cpp

#include <foo.h>
#include <iostream>

void foo() {
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

// main.cpp

#include <foo.h>
#include <iostream>

int main() {
    std::cout << __PRETTY_FUNCTION__ << '\n';
    foo();
}

Модули спасут отца русской демократии. Однажды. А пока что чем меньше .cpp файлов, тем быстрее будет сборка – при условии конечно, что у вас в .hpp есть какой-то код, а не сишные объявления. Ну и не стоит забывать про то, что чем больше информации о программе доступно компилятору, тем больше оптимизаций он сможет к ней применить.

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

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Ну например же https://herbsutter.com/2011/11/04/gotw-100-compilation-firewalls/

Не всегда нужно собрать все в один бинарь. А скорость сборки можно порешать пимплом и форвард декларейшонами.

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

Да, pimpl как пример случаев, когда разделение необходимо. При этом нужно понимать что pimpl это дорого, а потому без необходимости прибегать к этой идиоме не стоит.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

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

Если шаблонов мало и инлайнов мало, то до некоторого обьема кода время сборки всего проекта от числа единиц не зависит. Хотя если сборка параллельная, то зависит.

Но вот скорость ПЕРЕКОМПИЛЯЦИИ от числа единиц трансляции зависит, и ещё как. По каждому чиху пересобирать весь проект ну такое…

AntonI ★★★★★
()
Ответ на: комментарий от Siborgium

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

slackwarrior ★★★★★
()
Последнее исправление: slackwarrior (всего исправлений: 1)
Ответ на: комментарий от slackwarrior

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

Не всегда нужно собрать все в один бинарь.

Зачастую – нужно. Будь то исполняемый файл, статическая или динамическая библиотеки.

А скорость сборки можно порешать пимплом и форвард декларейшонами.

Пимпл не решает проблемы скорости сборки. Вернее, решает, но для не-шаблонного кода в С/C с классами стиле. Для С++ он скорость сборки ухудшает. Ключевая задача pimpl – спрятать детали реализации, чтобы они не протекали при подключении заголовочных файлов.

Форвард декларейшны а) нужно обновлять б) бесполезны для использования в шаблонном контексте. Это как раз тот самый С с классами стиль.

Siborgium ★★★★★
()
Ответ на: комментарий от slackwarrior

Нет, у pimpl нет преимуществ, кроме скрытия деталей реализации и обеспечивания стабильности ABI. Все остальные свойства pimpl – минусы.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Зачастую – нужно

А зачастую нет :)

Пимпл не решает проблемы скорости сборки. Вернее, решает, но для не-шаблонного кода в С/C с классами стиле.

А развесистый шаблониум тоже нужен не везде :)

Форвард декларейшны а) нужно обновлять б) бесполезны для использования в шаблонном контексте. Это как раз тот самый С с классами стиль.

Аргуметы от корнеркейса :) Т.е. достаточно контрпримера, которым пимпл и является :)

slackwarrior ★★★★★
()
Ответ на: комментарий от Siborgium

А пока что чем меньше .cpp файлов, тем быстрее будет сборка

Полная сборка - возможно (но далеко не факт). Только с точки зрения производительности труда людей интересует время инкрементальных. Никто в своём уме не будет по несколько часов ждать пока крупный проект целиком пересобирается после минимальных изменений.

bugfixer ★★★★★
()
Ответ на: комментарий от Siborgium

Да, есть преимущества :) клиентам насрать на шаблонную магию, а вот за скорость сборки бывает плюсик в карму :) т.к. им неинтересно почему оно «компиляется». У шаблонов тоже хватает недостатков. Главное не злоупотреблять любимым молотком, а то везде гвозди мерещатся.

slackwarrior ★★★★★
()
Ответ на: комментарий от slackwarrior

А зачастую нет :)

Демагогия. Объектники нужны очень редко.

А развесистый шаблониум тоже нужен не везде :)

Рекомендую открыть по порядку <vector> <iostream> <fmt> <utility> <tuple> <optional> <type_traits>, и конечно же <functional>. Особое внимание на первые два, которые включаются примерно везде.

От того, что вы лично «развесистый шаблониум» не написали, он никуда не пропадет.

Т.е. достаточно контрпримера, которым пимпл и является

Контрпримером чему пимпл является?

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Демагогия. Объектники нужны очень редко.

Не более чем твой шаблонный фанатизм :) при позднем связывании в плугинах очень даже норм

От того, что вы лично «развесистый шаблониум» не написали, он никуда не пропадет.

Лично я видел templates.cpp который собирался двое суток из-за злоупотребления авторами шаблониумом :)

Контрпримером чему пимпл является?

Сборке мира, из-за пары строчек, которая нужна не всем.

slackwarrior ★★★★★
()
Ответ на: комментарий от Siborgium

hpp (отказ от раздельной компиляции) замедляет компиляцию

Да?

На самом деле - «да». Только не надо демонстрировать циферки на пустых файлах. Чтобы доказать обратное Вам придётся нас всех убедить в том что ни один из проходов выполняемых компилятором не хуже O(N).

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

ни один из проходов

если метод а лучше метода б в 99 случаев из 100, а в 1 хуже, то, очевидно, метод а лучше, так как метод б в 99 случаев хуже.

не надо демонстрировать циферки на пустых файлах

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

zerhud
() автор топика
Ответ на: комментарий от zerhud

если метод а лучше метода б в 99 случаев из 100, а в 1 хуже, то, очевидно, метод а лучше, так как метод б в 99 случаев хуже.

Мне, безусловно, нравится Ваша целеустремлённость и уверенность. Вот только не работает оно так на практике. Мне приходится собираться десятки и сотни раз в день, и я бы умер ждать если бы каждый раз оно было с нуля. Если Вы вырастете за пределы «Hello, world» - поймёте (часть апликух с которыми я имею дело только линкуется больше 10 минут)…

ПыСы. Если Вы считаете что оптимизатор такой «волшебный» и вне зависимости от размера кода делает одинаково хорошую работу - то у меня для Вас сюрприз: у нас уже имеются .cpp которые (в частности) вызывают «variable tracking size limit exceeded with -fvar-tracking-assignments, retrying without» warnings. И я только что специально посмотрел - компиляция только одного из таких файлов занимает порядка 2.5 минут, на вполне себе неслабой билдовой машинке…

bugfixer ★★★★★
()
Ответ на: комментарий от Siborgium

От того, что вы лично «развесистый шаблониум» не написали, он никуда не пропадет.

Инклюд шаблонов не влияет почти ни на что, влияет инстацирование шаблонов.

И есть лайфхаки как ограничится инстацированием сложных шаблонов в одной единице трансляции и дальше линкер в помощь. У Вандервуда это описано.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от zerhud

если метод а лучше метода б в 99 случаев из 100, а в 1 хуже, то, очевидно, метод а лучше, так как метод б в 99 случаев хуже.

Если Вы свою статистику набрали на лично своих хеллоуворлдах и пытаетесь её теперь натянуть на весьмир, то это как бэ такое… Не надо так делать.

Скажем у нас типичный размер проекта первые тысячи строк кода, очень редко больше. Шаблонов овердофигища. И при этом единиц трансляции 2…5 шт, и это очень по делу, в тч потому что некоторые части компилятся долго из за шаблонов, а некоторые компилятся долго потому что SWIG там много всего нагенерил и оно толстое. Если бы это все собиралось одним куском то работать было бы невозможно.

AntonI ★★★★★
()
Ответ на: комментарий от bugfixer

Так в этом и дело. Есть проект, собирается почти два часа сейчас (не слабая тачка). Проект разделен на кучу всего. То есть подпроект, там либо elf либо so с интерфейсом и тесты. Вот ты там что то меняешь и начинают собираться штук 30-50 cpp файлов. А так собиралось бы в разы меньше. Мои проекты тоже из большого числа файлов состоят, но собрать нужно только тест который запускаешь, причем еще два три cpp файла собрать - не проблема, а вот десятки - уже тормозит.

zerhud
() автор топика
Ответ на: комментарий от AntonI

на лично своих хеллоуворлдах

почему хеллоуворлдах?

у меня например такая история. вот написал dsl, оформил в виде либы, там все разеделено было на cpp файлы, boost.spirit попытался спрятать, оно не вылезало никуда, если парсер менялся, то это затрагивало только некоторые файлы. наружу выходил ast, при этом парсер был доступен в тестах самого dsl. потом я заметил что на каждый чих перекомпилирую десяток файлов. сделал все header only и оказалось, что теперь компилирую один файл, котоырй компилировался и раньше, стало быстрее.

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

zerhud
() автор топика
Ответ на: комментарий от zerhud

Потому что у каждого свои хеллоуворлды.

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

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

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от zerhud

Лайфхак - вынести инстацирование всех шаблонов в отдельный обьектник и дальше с ним просто линковаться.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

свой личный опыт обобщить на весьмир

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

Задачи очень разные, методы их решения тоже очень разные

да на практике оказывается, что примерно все одинаково.

zerhud
() автор топика
Ответ на: комментарий от AntonI

да да, так я и делал. оказалось какой-то лажей. и еще в довесок куча трабл, потому что тебе нужно точно описать что инстанцировать. где-нибудь ошибся и уже ошибка линковки.

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

zerhud
() автор топика
Ответ на: комментарий от AntonI

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

zerhud
() автор топика
Ответ на: комментарий от AntonI

Инклюд шаблонов не влияет почти ни на что

Во-первых, влияет. Потому что

% wc -l main.cpp
7 main.cpp

превращается в

% g++ main.cpp -E -I. | wc -l
33744

строк, которые нужно как минимум распарсить, что в случае С++ является нетривиальной задачей. И так для каждого файла.

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

инстацированием сложных шаблонов в одной единице трансляции и дальше линкер в помощь

Это заведомо нерабочий подход, потому что шаблоны на то и шаблоны, что инстанциируются для множества типов, в том числе неизвестных в отдельно взятой единице трансляции. Не говоря уже об уникальных типах (decltype([]{})), да и самой по себе необходимости отслеживать используемые типы и заносить их в файл.

Siborgium ★★★★★
()
Ответ на: комментарий от slackwarrior

Не более чем твой шаблонный фанатизм

Вперед, выпиливайте STL. Постоянно вижу «антишаблонизаторов», которые призывают выпилить шаблоны из С++. Умные указатели, контейнеры и алгоритмы они почему-то выпиливать не хотят – загадка.

при позднем связывании в плугинах очень даже норм

Каждый .cpp файл порождает объектник, которые затем влинковываются в таргет. Так происходит с каждым .cpp файлом в каждом проекте на С и С++. Ваши плагины на этом фоне составляют мизерные доли процента.

Лично я видел templates.cpp который собирался двое суток из-за злоупотребления авторами шаблониумом :)

Верю. Разбейте на templates1.cpp и templates2.cpp – и будете собирать четверо суток.

Сборке мира, из-за пары строчек, которая нужна не всем.

Каким образом это контрпример?

Siborgium ★★★★★
()
Ответ на: комментарий от bugfixer

Только не надо демонстрировать циферки на пустых файлах.

Файлы не пустые – взгляните парой сообщений выше. Или 34 тысячи строк для вас какая-то шутка? А это ведь всего лишь <iostream>.

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

что ни один из проходов выполняемых компилятором не хуже O(N).

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

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

строк, которые нужно как минимум распарсить, что в случае С++ является нетривиальной задачей.

По сравнению с компиляцией с опцией -О3 парсинг это ерунда. Более того, у меня в некоторых проектах кодогенератор выдаёт такое шо без всяких шаблонов компилятор думает минут по дцать, и хорошо если в итоге не падает.

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

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

По сравнению с компиляцией с опцией -О3 парсинг это ерунда

Есть какие-то бенчи? Мне казалось я встречал инфу что от опций оптимизации время сборки сложного C++-кода зависит несильно. Ещё такие картинки есть https://www.phoronix.net/image.php?id=2019&image=llvm_time_trace_show https://aras-p.info/img/blog/2019/clang-timereport-teaser.png

utf8nowhere ★★★
()
Ответ на: комментарий от AntonI

По сравнению с компиляцией с опцией -О3 парсинг это ерунда.

вот тут жаловались на то что лишь добавление include некоторых хедеров без их использования стало замедлять компиляцию в С++20.

https://www.reddit.com/r/cpp/comments/o94gvz/what_happened_with_compilation_times_in_c20/

Так что парсинг тоже стоит времени, но да оптимизации больше времени занимают.

fsb4000 ★★★★★
()
Ответ на: комментарий от utf8nowhere

Есть личный опыт. Субьективно включение -О3 тормозит сборку на порядок как мин. Но понятно что это зависит от кода.

AntonI ★★★★★
()
Ответ на: комментарий от Siborgium

Или 34 тысячи строк для вас какая-то шутка?

«Напугали ежа голой жопой», что называется. Вот смотрю я на один из наших .cpp в 53k строчек (криминал, я знаю - corner case), который после препроцессора раздувается в 345k строк. И что?

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

Вот реально не думал что конкретно Вам мне это придётся разжёвывать…

Вам очевидно что тривиально показывается что если предел f(N) / g(N) при N стремящемся к бесконечности стремится к бесконечности, то для любого M найдется такой N что f(N) / (M * g(N/M)) больше единицы?

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 2)
Ответ на: комментарий от bugfixer

Вот смотрю я на один из наших .cpp в 53k строчек (криминал, я знаю - corner case), который после препроцессора раздувается в 345k строк. И что?

Это просто поразительно. У человека .cpp раздувается из-за шаблонов на 300k строк (в 7 раз), и он не видит никакой связи между продолжительностью сборки и количеством .cpp файлов.

Вам очевидно что тривиально показывается что если предел f(N) / g(N) при N стремящемся к бесконечности стремится к бесконечности, то для любого M найдется такой N что f(N) / (M * g(N/M)) больше единицы?

Хорошо, на зачет по матанализу за первый курс наработали. Теперь ответьте на заданный вопрос.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Хорошо, на зачет по матанализу за первый курс наработали.

Двойка мне, на самом деле. Давайте сначала договоримся согласны ли Вы со следующим:

Если предел f(N) / N при N стремящемся к бесконечности стремится к бесконечности, то для любого M > 1 найдется такой N’ что для всех N > N’ результат f(N) / (M * f(N / M)) больше единицы

?

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Давайте сначала договоримся согласны ли Вы со следующим:

Да, я согласен. Если это не так – будет любопытно посмотреть на выкладки.

Теперь жду ответ на мой вопрос. Если что, меня не волнует математическое обоснование, а волнует доказательство корректности ваших рассуждений.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Да, я согласен. Если это не так – будет любопытно посмотреть на выкладки.

Не думал что эта тема снова всплывёт. На самом деле утверждение ложно: можно показать что оно справедливо если «соревноваться» с N*log(N) (ie просто N - не хватает).

А в контексте раздельной компиляции это фундаментально - это означает что рано или поздно скомпилировать M кусочков размера N/M становится дешевле если «цена» компиляции выше O(N*log(N)), даже если это делать «в однопотоке». Это с точки зрения теории.

А с точки зрения практики - время инкрементальных билдов (и возможность их параллелить) - это то что народ заботит больше всего. И вот здесь стратегия «все засовываем в один TU» сливает по полной. Или это не очевидно?

bugfixer ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.