LINUX.ORG.RU

Кто как борется с фактическим отсутствием приватных объявлений в C++?

 , ,


0

4

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

 — непрозрачные ссылки на forward-объявления структур и функции для работы с ними;
 — публичная структура, которая агрегируется в класс-реализацию;
 — абстрактный класс, он же «интерфейс», от которого наследуется реализация.

Но у всех них есть свои недостатки. Есть ли какие-то иные приемы, которые я упустил?

★★★★

Последнее исправление: xaizek (всего исправлений: 1)

Не то чтоб часто пишу на С++, но выбираю вариант «перекомпилирую зависящий код».

PolarFox ★★★★★
()

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

Перекомпиляция происходит при любом изменении файла с кодом. Даже если пробел в комментарии подвинешь.

Бороться путём правильной архитектуры, чтобы пол проекта не пересобиралось от одного изменения, и forward declaration вместо include в заголовочных файлах.

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

Не то чтоб часто пишу на С++, но выбираю вариант «перекомпилирую зависящий код»

Хоп, и уже релизная сборка проекта на 50 тыс строк занимает полминуты. Перекомпилируйте дальше.

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

Бороться путём правильной архитектуры, чтобы пол проекта не пересобиралось от одного изменения, и forward declaration вместо include в заголовочных файлах

В любом проекте неизбежно есть какие-то центральные компоненты, которые используются большинством других модулей. Предлагаешь все базовые функции писать на forward-объявлениях и функциях? Тогда как бы возникает вопрос «а вы зачем C++ используете?».

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

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

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

Разбей эти классы на более мелкие и специализированные, чтобы можно было инклюдить только нужные конкретному файлу.

У меня была такая проблема: ccache, дробление популярных классов и файлов, forward declaration в заголовочных файлах, а инклюды только в .cpp

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

Код компилируется, зарплата капает

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

ox55ff ★★★★★
()

Есть ещё такой хак: 1) в заголовке пишешь определение класса без полей, но с декларациями деструктора, фабричных методов класса и публичных методов экземпляра, конструкторы удаляешь; 2) в единицу трансляции с реализацией этот заголовок не инклюдишь, а пишешь в ней определение класса ещё раз, уже с полями, приватными методами и прочим; 3) соответствие деклараций методов реализации проверяет линкер по мангленым именам.

iliyap ★★★★★
()

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

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

есть также вредное поверие собирать в огромные хидеры типа «все, что нужно». народе пресловутого windows.h

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

Хоп, и уже релизная сборка проекта на 50 тыс строк занимает полминуты.

Полминуты на проект? Хахахахахахах! У нас было по две-три минуты на один файл с кодом на Boost::Spirit >___<

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

У нас было по две-три минуты на один файл с кодом на Boost::Spirit >___<

вот еще одна причина вредности применения всяких там бустов и немеряных темплейтов на ровном месте.

alysnix ★★★
()

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

Из некрасивых решений можно упомянуть:

  • Некую форму динамической типизации. Методы интерфейса определяются в рантайме в момент вызова по строковому или числовому идентификатору.
  • Разбиение программы на несколько маленьких, которые общаются друг с другом на подобии микросервисов (для большинства случаев это слишком сурово).
  • Одна программа с неким механизмом обмена сообщениями. Как это любят делать в GUI.
pathfinder ★★★★
()
Ответ на: комментарий от iliyap

1) в заголовке пишешь определение класса без полей, но с декларациями деструктора, фабричных методов класса и публичных методов экземпляра, конструкторы удаляешь; 2) в единицу трансляции с реализацией этот заголовок не инклюдишь, а пишешь в ней определение класса ещё раз, уже с полями, приватными методами и прочим; 3) соответствие деклараций методов реализации проверяет линкер по мангленым именам

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

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

Методы интерфейса определяются в рантайме
подобии микросервисов
Одна программа с неким механизмом обмена сообщениями

Это уже какая-то скриптовуха пошла.

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

Это уже какая-то скриптовуха пошла.

Да, все так, это не универсальные решения, которые должны всем подойти.

pathfinder ★★★★
()

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

  • Forward Declarations
  • Интерфейсы
  • PIMPL
  • Dependency Injection
  • Не упарываться в ОПП
  • Не писать сложную бизнес логику на С++

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

Навскидку пришло в голову еще пару способов, скорее из system-design чем С++, но они косвенно тоже ускоряют компиляцию (и замедляют рантайм):

  • Динамическая линковка и плагины
  • Message passing внутри приложения
  • IPC (shared memory, RPC, REST/HTTP, etc.)
filosofia
()
Ответ на: комментарий от pathfinder

Этому есть общепринятое название, которое ТС было незнакомо.

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

Хоп, и уже релизная сборка проекта на 50 тыс строк занимает полминуты. Перекомпилируйте дальше.

Ну так учитесь писать правильно! Например, не надо бояться имплементацию выность в cpp-файлы.

А если кто-то злоупотребляет шаблонами — ну тогда ой. ССЗБ.

kawaii_neko ★★★★
()

непрозрачные ссылки на forward-объявления структур и функции для работы с ними;

This.

Кто как обходит проблему?

Не является проблемой.

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

увеличат скорость компиляции

Мизерно.

благотворно скажутся на общей архитектуре.

Никак, если общая архитектура уже грамотна.

При этом все это значительно замедляет рантайм.

Про ipc и сообщения даже пояснять нет смысла.

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

Пимпл это дрочево. Нужно для сохранения бинарной совместимости. В остальном только много лишней писанины и нет пользы.

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

Модули вроде должны эту херню исправить.

+1.

pr849
()

-ftime-trace и делать выводы

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

Модули вроде должны эту херню исправить.

и уже давно. В смысле кому-то должны. :-)

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

Есть ещё такой хак

Прямое нарушение ODR ломающее очень много чего. В приличных домах за такое…

bugfixer ★★★★★
()

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

это не «интерфейс класса», а его декларация. А декларация должна быть полной, поскольку размер обьекта класса должен быть вычислим при декларации переменной класса. а также вычислимы смещения методов в виртуальных таблицах.

и поскольку «зависящий код» зависит обычно именно в этом смысле - надо все перекомпилировать.

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

Пиши на Java/любом языке без магии сопроцессора

Если бы меня попросили назвать популярный язык, который хуже C++, то я бы проголосовал на Java. Код, который пишется в языках программирования в три строчки, в жаве пишется в три класса. А дальше уже не важно, насколько эти три класса «удобно» писать.

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

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

А еще потому, что любой код, использующий объявление класса, должен генерировать код инициализации и финализации приватных членов, плюс другие инлайн методы. Это такой «не интерфейс», что даже хочется назвать его интерфейсом. Какой же это «не интерфейс», если любой внешний код модифицирует приватные члены? Есть лишь нонсенс синтаксической ошибки, когда компилятор не дает модифицировать извне приватные члены больше, чем того позволяет стандарт.

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

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

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

Какой же это «не интерфейс», если любой внешний код модифицирует приватные члены?

Можно пример «любого внешнего кода модифицирующего приватные члены»?

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

причем тут «технические изьяны c++», если это будет справедливо для любого компилируемого языка со структурами. чтобы структуру разместить в памяти, надо знать ее размер. нельзя вычислить размер структуры, если ни видны все ее поля в точке вычисления размера.

тут модули всякие вообще не причем, модули ничего не изменят.

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

Если в системе сборки С++ появится возможность строить зависимости между структурами, функциями и прочим, тогда и поговорим про вычисление размеров структур и всего прочего (что тоже не самое сильное оправдание, хотя). А пока мы вообще-то все еще в каменном веке, и строим зависимости между файлами.

filosofia
()
Ответ на: комментарий от eao197

Можно пример «любого внешнего кода модифицирующего приватные члены»?

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

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

причем тут «технические изьяны c++», если это будет справедливо для любого компилируемого языка со структурами. чтобы структуру разместить в памяти, надо знать ее размер. нельзя вычислить размер структуры, если ни видны все ее поля в точке вычисления размера

Прикинь, в сишке можно зааллоцировать структуру, не знаю ее размера. Причем, с LTO оно еще и может быть заинлайнено.

byko3y ★★★★
() автор топика

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

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

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

Прикинь, в сишке можно зааллоцировать структуру, не знаю ее размера.

Вопиюще ложная информация.

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

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

Я уж было подумал, что вы перестали быть несущим откровенную херню балаболом. Но нет, не перестали.

Я давно пишу, что крестовые шаблоны — это макросы на кокаине стероидах.

Да совершенно фиолетово как вы относитесь к шаблонам. Они вот такие. Можно их любить, можно обсирать, но другими они не станут.

Нормальные люди принимают все как есть, но некоторым что-то где-то припекает, никак угомониться не могут.

eao197 ★★★★★
()

У меня обычно есть файл declarations, там лежат forward объявления. Иногда операторы всякие кладу. Короче он заточен под то, чтобы быть перманентным.

Иногда использую пимпл. У него есть подводный камень с константностью: указатель будет типа T* const а не const T* поэтому константные функции смогут «изменять» объект.

Хотя чаще всего делаю header only либы, и все ок, достаточно быстро компилируется.

Зы: сорян если что, весь тред не читал

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

Че? Как? Ты должен куда то ее поместить, нужно место в памяти.

Но возможно ты подразумевал не зная ее объявления? Так можно и в плюсах, нужна просто память куда ее положить. Но памяти должно быть достаточно , в любом случае.

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

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

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

это не «интерфейс класса», а его декларация. А декларация должна быть полной, поскольку размер обьекта класса должен быть вычислим при декларации переменной класса.

Могли бы сделать, чтобы в operator new передавался не размер класса, а указатель на дескриптор, содержащий размер. Тогда изменение размера приватных полей не приведёт к нарушению ABI. Так например в Оберонах сделано.

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

Не писать сложную бизнес логику на С++

Си тут никак не поможет. struct some_object *create_object(); – это тоже самое что и фабрика в C++. А прямое выделение памяти в клиенте создает туже самую проблему что и приватные поля класса.

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

Могли бы сделать, чтобы в operator new передавался не размер класса, а указатель на дескриптор, содержащий размер

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

int fun(){
  classA a;
  classB b; //<если неясен размер <a>, то непонятен адрес <b>.
}
alysnix ★★★
()
Ответ на: комментарий от alysnix

продолжение предыдущего поста:

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

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

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

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