В C ++ безопасно / переносимо использовать статический указатель на функцию-член для обратных вызовов C API? - PullRequest
26 голосов
/ 15 января 2010

В C ++ безопасно / переносимо использовать статический указатель на функцию-член для обратных вызовов C API? Является ли ABI статической функции-члена такой же, как функция C?

Ответы [ 4 ]

20 голосов
/ 15 января 2010

Это небезопасно в соответствии со стандартом C ++. Как указано в этой публикации :

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

И согласно комментариям, сделанным Мартином Йорком в этом ответе, существуют реальные проблемы с попытками сделать это на некоторых платформах.

Сделайте ваши обратные вызовы C ABI extern "C".


Редактировать: Добавление некоторых поддерживающих цитат из стандарта (выделено мной):

3,5 "Программа и связь":

После всех корректировок типов (во время которых typedefs (7.1.3) заменяются их определениями), типы, указанные во всех объявлениях, ссылающихся на данный объект или функцию, должны быть идентичными , за исключением объявлений для объекта массива можно указать типы массивов, которые отличаются наличием или отсутствием привязки основного массива (8.3.4). Нарушение этого правила для идентификации типа не требует диагностики. [3.5 / 10]

[Примечание: связь с объявлениями не-C ++ может быть достигнута с использованием спецификации связи (7.5). ] [3,5 / 11]

И

7,5 "Характеристики сцепления":

... Два типа функций с различными языковыми связями - это разные типы , даже если они идентичны. [7.5 / 1]

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

6 голосов
/ 15 января 2010

После поиска и нескольких перерывов при атаке на другие проблемы, я нашел ответ, который ясен и лаконичен (во всяком случае, для стандартного):

Вызов функции через выражение, тип функции которого имеет языковую связь, которая отличается от языковой связи типа функции определения вызываемой функции, не определена. [5.2.2 / 1]

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

В частности, это неопределенное поведение (и оно не использует библиотеку C, чтобы не возникало проблем):

void call(void (*pf)()) { pf(); } // pf() is the UB
extern "C" void f();
int main() { call(f); }
// though I'm unsure if a diagnostic is required for call(f)

Comeau дает диагностику на call(f) (хотя он может сделать это, даже если диагностика не требуется).

Это не неопределенное поведение, и показывает, как включить языковые связи в тип указателя на функцию (через typedef):

extern "C" typedef void F();
void call(F* pf) { pf(); }
extern "C" void f();
int main() { call(f); }

Или можно написать:

extern "C" {
typedef void F();
void f();
}
void call(F* pf) { pf(); }
int main() { call(f); }
5 голосов
/ 15 января 2010

Для всех известных мне компиляторов Windows C ++ ответ - да, но ничто в языковом стандарте не гарантирует этого. Однако я бы не позволил этому остановить вас, это очень распространенный способ реализации обратных вызовов с использованием C ++ - однако вы можете обнаружить, что вам нужно объявить статические функции как WINAPI. Это взято из моей старой библиотеки потоков:

class Thread {
   ...
   static DWORD WINAPI ThreadFunction( void * args );
};

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

3 голосов
/ 15 января 2010

ABI не покрывается ни стандартами C, ни C ++, хотя C ++ дает вам «языковую связь» через extern "C". Следовательно, ABI в основном зависит от компилятора / платформы. Оба стандарта оставляют много, много вещей до реализации, и это один из них.

Следовательно, написание 100% переносимого кода - или переключение компиляторов - трудно или невозможно, но предоставляет поставщикам и пользователям значительную гибкость в своих конкретных продуктах. Такая гибкость позволяет использовать более эффективные программы, позволяющие сэкономить пространство и время, таким образом, что комитеты по стандартизации не должны заранее их ожидать.


Насколько я понимаю, правила ISO не допускают применения стандарта чаще, чем раз в 10 лет (но могут быть различные публикации, такие как TC1 и TR1 для C ++). Плюс есть идея (я не уверен, что это исходит от ISO, перенесено из комитета C, или даже из других мест), чтобы «отогнать» / стандартизировать существующую практику вместо того, чтобы уходить в левую область, и есть 1007 * множество существующих практик, некоторые из которых противоречат.

...