Подтвердить что ты не робот

Как разработать API С++ для двоичной совместимой расширяемости

Я разрабатываю API для библиотеки С++, которая будет распространяться в dll/shared object. Библиотека содержит полиморфные классы с виртуальными функциями. Я обеспокоен тем, что, если я раскрываю эти виртуальные функции в DLL API, я избавляюсь от возможности расширения тех же классов с более виртуальными функциями, не нарушая бинарную совместимость с приложениями, созданными для предыдущей версии библиотеки.

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

Как вы создадите класс API, который может быть подклассифицирован в приложении, не теряя возможности расширять API с помощью (не абстрактных) виртуальных методов в новой версии dll при сохранении обратного двоичного соответствия?

Обновление: целевыми платформами для библиотеки являются windows/msvc и linux/gcc.

4b9b3361

Ответ 1

Несколько месяцев назад я написал статью под названием "Бинарная совместимость общих библиотек, реализованных в С++ в системах GNU/Linux" [pdf], Хотя концепции аналогичны в системе Windows, я уверен, что они не совсем то же самое. Но, прочитав статью, вы можете получить представление о том, что происходит на двоичном уровне С++, который имеет какое-либо отношение к совместимости.

Кстати, двоичный интерфейс приложения GCC суммируется в проекте стандартного документа " Itanium ABI", поэтому у вас будет формальное основание для выбранного стандарта кодирования.

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

Но в любом случае правила иногда слишком сложны для понимания. Поэтому вам может быть интересен инструмент, который проверяет совместимость двух заданных версий: abi-compliance-checker для Linux.

Ответ 3

C++ двоичная совместимость обычно сложна, даже без наследования. Посмотрите, например, на GCC. За последние 10 лет я не уверен, сколько избило изменения ABI, которые у них были. Тогда MSVC имеет другой набор условных обозначений, поэтому связь с GCC и наоборот не может быть выполнена... Если вы сравните это с миром C, компилятор будет лучше там.

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

Ответ 4

Я думаю, вы неправильно поняли проблему подкласса.

Вот ваш Pimpl:

// .h
class Derived
{
public:
  virtual void test1();
  virtual void test2();
private;
  Impl* m_impl;
};

// .cpp
struct Impl: public Base
{
  virtual void test1(); // override Base::test1()
  virtual void test2(); // override Base::test2()

  // data members
};

void Derived::test1() { m_impl->test1(); }
void Derived::test2() { m_impl->test2(); }

Видите? Нет проблем с переопределением виртуальных методов Base, вам просто нужно убедиться, что они обновили их virtual в Derived, чтобы те, которые получены из Derived, знали, что они могут переписать их тоже (только если вы этого хотите, путь - отличный способ предоставить final тем, у кого его нет), и вы все еще можете переопределить его для себя в Impl, который может даже назвать версию Base.

Нет проблем с Pimpl.

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

Ответ 5

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