Этот вопрос уже задан на этом форуме, но я не понимаю эту концепцию.
Я читал, и кажется, что сигнал и слоты реализованы с использованием указателей функций, т.е. сигнал является одной большой функцией, внутри которой он вызывает все подключенные слоты (указатели функций). Это верно? И какова роль сгенерированных файлов moc во всей истории? Я не понимаю, как функция сигнала знает, какие слоты для вызова, какие слоты подключены к этому сигналу.
Ответ 1
Qt реализует эти вещи таким образом, который напоминает интерпретируемые языки. То есть он строит таблицы символов, которые отображают имена сигналов в указатели функций, поддерживает их и ищет нужную функцию по имени функции.
Каждый раз, когда вы излучаете сигнал, т.е. записываете
emit something();
вы на самом деле вызываете функцию something()
, которая автоматически генерируется компилятором метаобъекта и помещается в файл *.moc
. В рамках этой функции он проверял, к каким слотам подключен этот сигнал в настоящий момент, и соответствующие функции слотов (которые вы реализовали в ваших собственных источниках) последовательно вызываются через таблицы символов (описанным выше способом). И emit
, как и другие ключевые слова Qt, просто отбрасываются препроцессором С++ после создания *.moc
. Действительно, в одном из заголовков Qt (qobjectdefs.h) существуют такие строки:
#define slots
#define signals protected
#define emit
Функция соединения (connect
) просто изменяет таблицы символов, поддерживаемые в файлах *.moc
, и переданные ей аргументы (с помощью SIGNAL()
и `SLOT macros) также предварительно обрабатываются для соответствия таблицам.
Это общая идея. В своем другом ответе ジ ョ ー ジ предоставляет нам ссылки в список рассылки trolltech и другой вопрос SO по этой теме.
Ответ 2
Думаю, я должен добавить следующее.
Есть еще один связанный вопрос - и есть очень хорошая статья, который можно рассматривать как довольно подробное расширение для него answer; вот эта статья снова, с улучшенной (хотя и не идеальной) подсветкой синтаксиса кода.
Здесь мой короткий пересказ, который может быть подвержен ошибкам)
В основном, когда мы вставляем макрос Q_OBJECT
в наше определение класса, препроцессор расширяет его до статического объявления экземпляра QMetaObject
, которое будет использоваться всеми экземплярами одного и того же класса:
class ClassName : public QObject // our class definition
{
static const QMetaObject staticMetaObject; // <--= Q_OBJECT results to this
// ... signal and slots definitions, other stuff ...
}
Этот экземпляр, в свою очередь, при инициализации будет хранить сигнатуры ("methodname(argtype1,argtype2)"
) сигналов и слотов, что позволит реализовать вызов indexOfMethod()
, который возвращает, ну, индекс метода по его подписи:
struct Q_CORE_EXPORT QMetaObject
{
// ... skip ...
int indexOfMethod(const char *method) const;
// ... skip ...
static void activate(QObject *sender, int signal_index, void **argv);
// ... skip ...
struct { // private data
const QMetaObject *superdata; // links to the parent class, I guess
const char *stringdata; // basically, "string1\0string2\0..." that contains signatures and other names
const uint *data; // the indices for the strings in stringdata and other stuff (e.g. flags)
// skip
} d;
};
Теперь, когда moc
создает файл moc_headername.cpp
для заголовка класса Qt headername.h
, он помещает там строки подписи и другие данные, необходимые для правильной инициализации структуры d
, а затем записывает код инициализации для staticMetaObject
singleton с использованием этих данных.
Еще одна важная вещь - генерация кода для метода object qt_metacall()
, который принимает идентификатор объекта объекта и массив указателей аргументов и вызывает метод с помощью длинного switch
следующим образом:
int ClassName::qt_metacall(..., int _id, void **_args)
{
// ... skip ...
switch (_id) {
case 0: signalOrSlotMethod1(_args[1], _args[2]); break; // for a method with two args
case 1: signalOrSlotMethod2(_args[1]); break; // for a method with a single argument
// ... etc ...
}
// ... skip ...
}
Наконец, для каждого сигнала moc
генерируется реализация, которая содержит вызов QMetaObject::activate()
:
void ClassName::signalName(argtype1 arg1, argtype2 arg2, /* ... */)
{
void *_args[] = { 0, // this entry stands for the return value
&arg1, // actually, there a (void*) type conversion
&arg2, // in the C++ style
// ...
};
QMetaObject::activate( this,
&staticMetaObject,
0, /* this is the signal index in the qt_metacall() map, I suppose */
_args
);
}
Наконец, вызов connect()
переводит сигнатуры строкового метода в их целые идентификаторы (те, которые используются qt_metacall()
), и поддерживает список соединений "сигнал-слот"; когда сигнал испускается, код activate()
проходит через этот список и вызывает соответствующие "слоты" объекта через их метод qt_metacall()
.
Подводя итог, статический экземпляр QMetaObject
хранит "метаинформацию" (строки подписи метода и т.д.), сгенерированный метод qt_metacall()
предоставляет "таблицу методов", которая позволяет любому сигналу/слоту вызываться посредством индекс, реализации сигналов, сгенерированные с помощью moc
, используют эти индексы через activate()
, и, наконец, connect()
выполняет работу по поддержанию списка карт индексов "сигнал-слот".
* Примечание: существует сложность этой схемы, используемой для случая, когда мы хотим доставлять сигналы между разными потоками (я подозреваю, что нужно смотреть на код blocking_activate()
), но я надеюсь, что общая идея остается той же )
Это мое очень грубое понимание связанной статьи, которая легко может быть неправильной, поэтому я рекомендую идти и читать ее напрямую)
PS. Поскольку я хотел бы улучшить свое понимание реализации Qt - сообщите мне о любых несоответствиях в моем пересказе!
Поскольку мой другой (более ранний) ответ был удалён каким-то ревностным редактором, я добавлю здесь текст (мне не хватает нескольких подробностей, которые не были включены в сообщение Павла Швед, и я сомневаюсь, что человек, который удалил ответ, позаботился).
@Pavel Shved:
Я уверен, что где-нибудь в заголовках Qt существует строка:
#define emit
Просто для подтверждения: нашел его в старом Qt-коде с помощью Google Code Search. Вполне вероятно, что он все еще существует); путь найденного местоположения:
ftp://ftp.slackware-brasil.com.br > Slackware-7.1 > вно > КДЭ-1,90 > кварты-2.1.1.tgz > USR > Lib > кварты-2.1.1 > ЦСИ > ядро > qobjectdefs.h
Другая дополнительная ссылка: http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html - см. ответ Андреаса Пакулата
И вот еще одна часть ответа: Вопрос Qt: Как работают сигналы и слоты?