Двоичная совместимость при использовании передачи по ссылке вместо передачи по указателю

Этот вопрос задуман как продолжение этого вопроса: Каковы различия между переменной-указателем и переменной-ссылкой в ​​C++?

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

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

Рассмотрим следующий код:

class IMediaSerializable
{
public:
    virtual tResult Serialize(int flags,
                              ISerializer* pSerializer,
                              IException** __exception_ptr) = 0;
//[…]
};

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

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

class IMediaSerializable
{
public:
    virtual tResult Serialize(int flags,
                              ISerializer& pSerializer,
                              IException*& __exception_ptr) = 0;
//[…]
};

Кажется, это работает без каких-либо недостатков. Но нам остается вопрос, удовлетворяет ли это требование бинарной совместимости.

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


comment
Даже если он совместим с двоичными файлами, имена функций будут искажены по-разному, поэтому он не будет связан.   -  person sbabbi    schedule 12.02.2015
comment
Уточнил вопрос, надеюсь теперь проблема прояснится.   -  person PiJ    schedule 12.02.2015
comment
POD ушел (хорошо разобранный) в C++11: теперь у нас есть стандартная компоновка, которая дает много возможностей в классе. Вы можете передавать регуляризованные сложные объекты так же надежно, как и типы POD.   -  person Yakk - Adam Nevraumont    schedule 12.02.2015


Ответы (3)


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

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

person Sam Varshavchik    schedule 12.02.2015

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

Однако ваше ограничение на чисто абстрактные классы и типы POD слишком усердно в эпоху C++11.

C++11 разделил концепцию модуля на несколько частей. Standard Layout покрывает большую часть, если не все, гарантий «раскладки памяти» типа pod.

Но типы Standard Layout могут иметь конструкторы и деструкторы (помимо других различий).

Таким образом, вы можете сделать действительно дружественный интерфейс.

Вместо управляемого вручную указателя интерфейса напишите простой интеллектуальный указатель.

template<class T>
struct value_ptr {
  T* raw;
  // ...      
};

что ->clone()s при копировании, перемещает указатель при перемещении, удаляет при уничтожении и (поскольку он принадлежит вам) может быть гарантированно стабильным в версиях библиотеки компилятора (в то время как unique_ptr не может). Это в основном unique_ptr, который поддерживает ->clone(). Также имейте свой собственный unique_ptr для значений, которые не могут быть продублированы.

Теперь вы можете заменить свои чистые виртуальные интерфейсы парой типов. Во-первых, чистый виртуальный интерфейс (обычно с T* clone() const), а во-вторых, обычный тип:

struct my_regular_foo {
  value_ptr< IFoo > ptr;
  bool some_method() const { return ptr->some_method(); } // calls pure virtual method in IFoo
};

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

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

И расширение интерфейса IFoo в порядке. Просто добавьте новый метод к IFoo в конце типа (который в большинстве ABI обратно совместим (!) — попробуйте), а затем добавьте новый метод к my_regular_foo, который перенаправляет его. Поскольку мы не изменили макет нашего my_regular_foo, даже если код библиотеки и клиентский код могут расходиться во мнениях относительно того, какие у нее есть методы, это нормально — все эти методы скомпилированы встроенными и никогда не экспортируются — и клиенты, которые знают, что они используют более новая версия вашей библиотеки может ее использовать, а те, кто не знает, но использует ее, прекрасно (без пересборки).

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

Этот последний шаг — разрешение новых методов в ваших интерфейсах — может стать мостом к далекому пути, поскольку вам придется исследовать гарантии ABI (на практике и нет) в макете виртуальной таблицы для каждого поддерживаемого компилятора.

person Yakk - Adam Nevraumont    schedule 12.02.2015
comment
Хотелось бы, чтобы я приблизился к обобщенному POD в С++ 11, это в первую очередь решило бы некоторые из наших проблем с API. Чтобы изучить более обобщенный POD и лучше понять ваши предложения, я прочитал некоторые основы в Википедии и у Бьярнеса превосходно Часто задаваемые вопросы по C++11. Копаем глубже благодаря вашему ответу. - person PiJ; 13.02.2015

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

Рассмотрим класс Foo, который экспортирует две функции:

class Foo
{
public:
     void f(int*);
     void f(int&);
};

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

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

Например, GCC искажает эти имена так:

void Foo::f(int*) => _ZN3Foo1fEPi
void Foo::f(int&) => _ZN3Foo1fERi

Обратите внимание на P против R.

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

person sbabbi    schedule 12.02.2015
comment
Уточнил вопрос, надеюсь теперь проблема прояснится. - person PiJ; 12.02.2015