Можете Вы бросать указатель функции этого типа:
void (*one)(int a)
к одному из этого типа:
void (*two)(int a, int b)
и затем безопасно вызовите указанный функция с дополнительными аргументами (аргументами), которые она была брошена для взятия? Я думал, что такая вещь была недопустима, это, оба функциональных типа должны были быть совместимыми. (Значение того же прототипа - то же возвращаемое значение, тот же список параметров.), Но это точно, что этот бит GTK + код, кажется, делает (взятый отсюда):
g_signal_connect_swapped(G_OBJECT(button), "clicked",
G_CALLBACK(gtk_widget_destroy), G_OBJECT(window));
Если Вы будете искать "нажатый" сигнал (или просто посмотрите на другие примеры его использования из первой ссылки), то Вы будете видеть, что его обработчики, как ожидают, будут объявлены как это:
void user_function(GtkButton *button, gpointer user_data);
При регистрации обработчика через g_signal_connect_swapped (), указатель виджета и аргументы указателя данных подкачиваются в порядке, таким образом, объявление должно быть похожим на это вместо этого:
void user_function(gpointer user_data, GtkButton *button);
Вот проблема. gtk_widget_destroy () функция, зарегистрированная как обратный вызов, смоделирован как это:
void gtk_widget_destroy(GtkWidget *widget);
взять только отдельный аргумент. По-видимому, потому что указатель данных (GtkWindow) и указатель на сигнальный виджет (GtkButton) подкачиваются, единственным аргументом, который он получает, будет указатель окна, и указатель кнопки, который будет передан после, будет тихо проигнорирован. Некоторый поиск с помощью Google поднял подобные примеры, даже регистрация функций как gtk_main_quit (), которые не берут аргументов вообще.
Я корректен в том, чтобы полагать, что это нарушение стандартов? Имеют GTK +, разработчики нашли некоторое легальное волшебство сделать эту всю работу?
В соответствии с конвенцией по вызову C на вызывающего абонента возлагается ответственность за очистку аргументов в стеке. Таким образом, если вызывающий абонент предоставляет слишком много аргументов, это не является проблемой. Дополнительные аргументы просто игнорируются.
Так что да, вы можете привести указатель функции к другому типу указателя функции с теми же аргументами, а затем к некоторым, и вызвать исходную функцию с слишком большим количеством аргументов, и это сработает.
Я полагаю, что одна вещь, которую вы не упомянули, это пространство и распределение. Примитивы являются типами значений и назначаются в стеке (если они не связаны с объектом), за исключением типа последовательности, как вы упомянули (класс последовательности выделяет свое пространство в куче).
Хотя сами объекты содержат примитивы, там место хранения находится место назначения фактического объекта, находящегося в куче.
Кроме того, что ваше заявление довольно хорошо написано. У вас есть конкретный вопрос, который я пропустил:)?
-121--2805577- Вот как вы думаете о виртуальных методах. Каждый экземпляр класса имеет «поля» для хранения методов. Когда вы помечаете метод как виртуальный
, он говорит сделать новый «ящик» и поместить в него метод. Когда метод помечается как override
в производном классе, он сохраняет «box» из базового класса, но помещает в него новый метод.
Здесь имеется класс A
и метод с именем mVVirtual
, помеченный как virtual
. Это означает, что создайте новый «ящик» с именем mVVirtual
и поместите в него метод с определением
Console.WriteLine("A::mVVirtual");
Тогда у вас есть производный класс B
и метод с именем mVVirtual
, который помечен как virtual
. Это говорит сделать новый «ящик» с именем mVVirtual
и поместить в него метод с определением
Console.WriteLine("B::mVVirtual");
В частности, «ящик», унаследованный от A
, скрыт! Его не могут видеть объекты, которые набраны как B
, или классы, производные от B
.
Имеется производный класс C
и метод с именем mVVirtual
, помеченный как override
. Это говорит взять «ящик» с именем mVVirtual
, унаследованный от B
, и поместить в него другой метод с определением
Console.WriteLine("C::mVVirtual");
Теперь, когда у вас есть
B b1 = new C();
b1.mVVirtual();
вы говорите компилятору, что b1
является B
так что b1.mVVirtual()
ищет в «box» mVVirtual
и находит метод с определением
Console.WriteLine("C::mVVirtual");
, поскольку b1
действительно является C
, и именно это находится в «box» mVVirtual
для экземпляров C
.
Но когда у вас есть
A a2 = new C();
a2.mVVirtual();
вы говорите компилятору, что a2
является A
и поэтому он смотрит в «поле» и находит
Console.WriteLine("A::mVVirtual");
Компилятор не может знать, что a2
действительно является C
(вы ввели его как A
), поэтому не известно, что a2
действительно является экземпляром класса, производным от класса, который скрыл «box» mVVirtual
, определенный A
. Известно, что A
имеет «box» с именем mVVirtual
и поэтому выдает код для вызова метода в этом «box».
Таким образом, чтобы попытаться поставить это лаконично:
class A {
public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}
определяет класс, который имеет «box» с полным именем A:: mVVirtual
, но что вы можете сослаться на mVVirtual
.
class B : A
{
// "new" method; compiler will tell you that this should be marked "new" for clarity.
public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}
определяет класс, который имеет "поле" с полным именем B:: mVVirtual
, но на который можно ссылаться под именем mVVirtual
. Ссылка на B.mVVirtual
не относится к "ящику" с полным именем A:: mVVirtual
; это "поле" не может быть отображено объектами, набранными как B
(или классами, производными от B
).
class C : B
{
public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}
определяет класс, который принимает "box" с полным именем B:: mVVirtual
и помещает в него другой метод.
Тогда
A a2 = new C();
a2.mVVirtual();
говорит, что a2
является A
, так что a2.mVVirtual
выглядит в "поле" с полным именем A:: mVVirtual
и вызывает метод в этом "поле". Вот почему вы видите
A::mVVirtual
на консоли.
Существует два других аннотатора методов. аннотация
делает новое "поле" не помещает определение метода в "поле". new
создает новый "box" и помещает определение метода в "box", но не позволяет производным классам поместить свои собственные определения метода в "box" (используйте virtual
, если вы хотите это сделать).
Извините за долголетие, но я надеюсь, что это поможет.
-121--3566144-На мой взгляд, C89 стандарты в этом отношении довольно запутаны. Насколько я знаю, они не запрещают кастинг от/к функции без спецификации param, поэтому:
typedef void (*one)(int first);
typedef void (*two)(int first, int second);
typedef void (*empty)();
one src = something;
two dst;
/* Disallowed by C89 standards */
dst = (two) src;
/* Not disallowed by C89 standards */
dst = (two) ((empty) src);
В конце компиляторы должны уметь приводить от одного
к двум
, поэтому я не вижу причины запрещать прямой актёрский состав.
В любом случае, обработка сигнала в GTK + использует некоторую темную магию за кадром для управления обратными вызовами с различными шаблонами аргументов, но это другой вопрос.
Теперь, насколько круто в том, что я также в настоящее время проходит свой путь через учебное пособие GTK и наткнулся на такую же проблему.
Я попробовал несколько примеров, а затем задал вопрос Что произойдет, если я бросил указатель функции, изменив количество параметров , с упрощенным примером.
Ответ на ваш вопрос (адаптирован из прекрасных ответов на мой вопрос выше, а ответы от вопросов указатель функции отлив на разную подпись ):
Итак, реальный вопрос, почему разработчики Glib это понравилось. Но это другой вопрос, который я думаю ...