Расщепление длинного метода, поддерживающего интерфейс класса - PullRequest
1 голос
/ 07 декабря 2011

В моей библиотеке есть такой класс:

class Foo {
public:
    void doSomething();
};

Теперь реализация doSomething() сильно выросла, и я хочу разделить ее на два метода:

class Foo {
public:
    void doSomething();
private:
    void doSomething1();
    void doSomething2();
};

Где doSomething() это реализация:

void Foo::doSomething() {
    this->doSomething1();
    this->doSomething2();
}

Но теперь интерфейс класса изменился. Если я скомпилирую эту библиотеку, все существующие приложения, использующие эту библиотеку, не будут работать, внешняя связь будет изменена.

Как избежать нарушения бинарной совместимости?

Полагаю, встраивание решает эту проблему. Это правильно? И это портативный? Что произойдет, если оптимизация компилятора отключит эти методы?

class Foo {
public:
    void doSomething();
private:
    inline void doSomething1();
    inline void doSomething2();
};

void Foo::doSomething1() {
    /* some code here */
}

void Foo::doSomething2() {
    /* some code here */
}

void Foo::doSomething() {
    this->doSomething1();
    this->doSomething2();
}

EDIT: Я тестировал этот код до и после разделения метода, и он, кажется, поддерживает двоичную совместимость. Но я не уверен, что это будет работать в каждой ОС, в каждом компиляторе и с более сложными классами (с виртуальными методами, наследованием ...). Иногда у меня возникало нарушение двоичной совместимости после добавления таких закрытых методов, но сейчас я не помню, в какой конкретной ситуации. Возможно, это произошло из-за того, что таблица символов просматривается по индексу (например, Стив Джессоп отмечает в своем ответе).

Ответы [ 3 ]

2 голосов
/ 07 декабря 2011

Строго говоря, изменение определения класса вообще (любым из показанных вами способов) является нарушением правила единого определения и ведет к неопределенному поведению.

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

Таким образом, на практике изменение таблицы символов не имеет значения при условии, что динамический компоновщик ищет записи в таблице символов по имени. В таблице символов больше записей, чем раньше, но это нормально, потому что все старые имеют одинаковые искаженные имена. Может случиться так, что с вашей реализацией частные и / или встроенные функции (или любые указанные вами функции) не экспортируются dll, но вам не нужно полагаться на это.

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

Итак, вы можете проверить документацию по C ++ ABI или документацию по компилятору / компоновщику, чтобы быть абсолютно уверенным, или просто поверить мне на слово и продолжить.

0 голосов
/ 07 декабря 2011

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

0 голосов
/ 07 декабря 2011

Здесь нет проблем. Имя Foo::doSomething() всегда одинаково, независимо от его реализации.

...