С++ - при повторной компиляции
У вас есть класс, от которого зависят многие библиотеки. Вам нужно изменить класс для одного приложения. Какие из следующих изменений требуют перекомпиляции всех библиотек, прежде чем можно будет безопасно создавать приложение?
- добавить конструктор
- добавить элемент данных
- изменить деструктор на виртуальный
- добавить аргумент со значением по умолчанию к существующей функции-члену
Спасибо
Ответы
Ответ 1
Классы определены в файле заголовка. Файл заголовка будет скомпилирован в библиотеку, которая реализует класс и код, который использует класс. Я предполагаю, что вы принимаете за задание, что вам нужно будет перекомпилировать реализацию класса после изменения файла заголовка класса и что вопрос, который вы задаете, заключается в необходимости перекомпилировать любой код, который ссылается на класс.
Проблема, которую вы описываете, является одной из двоичной совместимости (BC) и обычно следует следующим правилам:
- Добавление не виртуальных функций в любом месте класса не прерывает BC.
- Изменение любого определения функции (добавление параметров) приведет к разрыву BC.
- Добавление виртуальных функций в любом месте изменяет v-таблицу и поэтому прерывает BC.
- Добавление членов данных приведет к разрыву BC.
- Изменение параметра от нестандартного по умолчанию не приведет к разрыву BC.
- Любые изменения встроенных функций будут прерывать BC (поэтому следует избегать встроенной функции, если BC важен.)
- Изменение компилятора (или иногда даже версий компилятора), вероятно, приведет к разрыву BC, если компиляторы не будут привязаны к одному и тому же ABI.
Если BC является серьезной проблемой для платформы, которую вы реализуете, вполне может быть хорошей идеей разделить интерфейс и реализацию с помощью Bridge.
В стороне, язык С++ не имеет отношения к двоичному интерфейсу приложения (ABI). Если двоичная совместимость является серьезной проблемой, вы можете, вероятно, обратиться к спецификации своей платформы ABI для более подробной информации.
Изменить: обновлено добавление элементов данных. Это сломает BC, потому что для класса теперь потребуется больше памяти, чем раньше.
Ответ 2
Строго говоря, вы попадаете в Undefined Поведение земли, как только вы не перекомпилируете по какой-либо из этих причин.
Тем не менее, на практике вы можете избавиться от нескольких из них:
Возможно, будет полезно использовать до тех пор, пока
- это не первый определяемый пользователем конструктор класса
- это не конструктор копирования
Это изменяет размер экземпляров класса. Может быть хорошо для всех, кто просто использует указатели или ссылки, если вы позаботите о том, чтобы эти данные остались за всеми другими данными, чтобы смещения для доступа к другим членам данных не изменились. Но точное расположение вспомогательных объектов в двоичном формате не определено, поэтому вам придется полагаться на конкретную реализацию.
- изменить деструктор на виртуальный
Это изменяет виртуальную таблицу класса, поэтому она нуждается в перекомпиляции.
- добавить аргумент со значением по умолчанию к существующей функции-члену
Поскольку аргументы по умолчанию вставлены на сайт вызова, все, кто использует это, должны перекомпилировать. (Однако, используя перегрузку вместо аргументов по умолчанию, вы можете избежать этого.)
Обратите внимание, что любая встроенная функция-член может отобразить любую из вышеперечисленных ошибок, поскольку код из них непосредственно встроен (и оптимизирован) в код клиента.
Однако самая безопасная ставка состояла бы в том, чтобы просто перекомпилировать все. Почему это проблема?
Ответ 3
Все они должны перекомпилировать все библиотеки, которые используют этот класс. (если они содержат файл .h)
Ответ 4
Ответ sbi довольно хорош (и заслуживает того, чтобы его проголосовали вверх). Однако я думаю, что могу расширить "возможно, хорошо" на что-то более конкретное.
-
Добавить конструктор
Если конструктор, который вы добавили, является конструктором по умолчанию (или действительно конструктором копирования), тогда вы должны быть осторожны. Если ранее не было доступно, они автоматически генерировались компилятором (поскольку такая перекомпиляция требуется для обеспечения того, что они используют фактический конструктор, который был реализован). По этой причине я склонен всегда скрывать или определять эти конструкторы для классов, которые образуют некоторый API.
Ответ 5
Используя стандартный файл экспорта .def для поддержки Application Binary Interface, вы можете избежать перекомпиляции клиентов во многих случаях:
-
Добавить конструктор
Экспортировать эту функцию конструктора в
конец таблицы экспорта с наибольшими
порядковый номер. Любой клиентский код
не требует этой потребности конструктора
не компилировать.
-
Добавить элемент данных
Это разрыв, если клиентский код манипулирует объектом класса напрямую, а не через указатель или ссылку.
-
Измените деструктор на виртуальный
Это, вероятно, перерыв, если ваш
класс не имеет другого виртуального
функция, что означает теперь ваш класс
должен добавить таблицу vptr и увеличить
размер объекта объекта и память изменения
layour. Если ваш класс уже
иметь таблицу vptr, перемещение деструктора
до конца таблицы vptr не повлияет
макет объекта с точки зрения отставания
совместимость. Но если класс клиента получен из вашего класса и определил его собственную виртуальную функцию, тогда он ломается. А также любой клиентский вызов
оригинальный не виртуальный деструктор сломается.
-
Добавьте аргумент со значением по умолчанию
существующая функция-член
Это определенно разрыв.
Ответ 6
Я явно против @sbi ответа: в общем, вам нужно перекомпилировать. Только при гораздо более строгих обстоятельствах, чем те, которые он опубликовал, вы можете уйти.
Если добавленный конструктор является либо конструктором по умолчанию, либо конструктором копирования, любой код, который использовал неявно определенную версию и не перекомпилирует, не сможет инициализировать объект, а это означает, что инварианты, требуемые другими методами, не будут быть установленным при построении, т.е. код не будет выполнен.
Это изменяет макет объекта. Даже код, который использовал только указатели или ссылки, должен быть перекомпилирован для адаптации к изменению макета. Если член добавлен в начале объекта, любой код, который использовал какой-либо элемент объекта, будет смещен и сработает.
struct test {
// int x; // added later
int y;
};
void foo( test * t ) {
std::cout << t->y << std::endl;
}
Если foo
не перекомпилировалось, то после раскола x
он будет печатать t->x
вместо t->y
. Если типы не совпадают, это будет даже хуже. Теоретически, даже если добавленный элемент находится в конце объекта, если есть более одного модификатора доступа, компилятору разрешено изменять порядок членов и удалять ту же проблему.
- изменить деструктор на виртуальный
Если это первый виртуальный метод, он изменит макет объекта и получит все предыдущие проблемы, плюс добавление, которое удаление с помощью ссылки на базу вызовет базовый деструктор и не будет отправлено на правильный метод. В большинстве компиляторов (с поддержкой vtable) это может означать изменение в макете памяти vtable для типа, а это означает, что неправильный метод можно вызвать и вызвать хаос.
- добавить аргумент со значением по умолчанию
Это изменение в сигнатуре функции, весь код, который использовал этот метод, должен быть перекомпилирован для адаптации к новой сигнатуре.
Ответ 7
Как только вы меняете что-либо в файле заголовка (файл hpp), вам нужно перекомпилировать все, что от него зависит.
Однако, если вы измените исходный файл (файл cpp), вам нужно перекомпилировать только библиотеку, которая содержит определения потребностей из этого файла.
Легкий способ разбить физические зависимости, где все библиотеки верхнего уровня необходимо перекомпилировать, - использовать идиому pimpl. Затем, пока вы не трогаете файлы заголовков, вам просто нужно скомпилировать библиотеку, в которой будет изменена реализация.