Qt Signals Slots Emit

broken image


Qt signals slots emit signal

What the code below does is to create a slot and signal with a function that will emit a signal when called (the function that is) so the signal is 'fired' off to what ever is listening to it, to connect a signal to a slot you use the QObject::connect as below. In Qt, we have an alternative to the callback technique: We use signals and slots. A signal is emitted when a particular event occurs. Qt's widgets have many predefined signals, but we can always subclass widgets to add our own signals to them. A slot is a function that is called in response to a particular signal. Signal and Slots A signal is emitted when a particular event occurs. Qt Jambi's widgets have many predefined signals, but we can always subclass widgets to add our own signals to them. A slot is a method that is called in response to a particular signal. How does the behaviour differ for direct and queued signal-slot connections? What changes if we emit the object by value or receive it by value? Nearly every customer asks this question at some point in a project. The Qt documentation doesn't say a word about it.

Qt中的类库有接近一半是从基类QObject上继承下来,信号与反应槽(signals/slot)机制就是用来在QObject类或其子类间通讯的方法。作为一种通用的处理机制,信号与反应槽非常灵活,可以携带任意数量的参数,参数的类型也由用户自定。同时其本身也是类型安全的,任何一个从QObject或其子类继承的用户类都可以使用信号与反应槽。
Slots
信号的作用如同Windows系统中的消息。在Qt中,对于发出信号的对象来说,它并不知道是谁接收了这个信号。这样的设计可能在某些地方会有些不便,但却杜绝了紧耦合,于总体设计有利。反应槽是用来接收信号的, 但它实际上也是普通的函数,程序员可以象调用普通函数一样来调用反应槽。与信号类似的是,反应槽的拥有者也不知道是谁向它发出了信号。在程序设计过程中,多个信号可以连接至一个反应槽,类似的,一个信号也可以连接至多个反应槽,甚至一个信号可以连接至另一个信号。
在Windows中,如果我们需要多个菜单都激发一个函数,一般是先写一个共用函数,然后在每个菜单的事件中调用此函数。在Qt中如果要实现同样的功能,就可以把实现部分写在一个菜单中,然后把其他菜单与这个菜单级联起来。
虽然信号/反应槽机制有很多优点,使用也很方便,但它也不是没有缺点。最大的缺点在于要稍微牺牲一点性能。根据Trolltech公司的自测,在CPU为Intel PentiumII 500 Mhz的PC机上,对于一个信号对应一个反应槽的连接来说,一秒钟可以调用两百万次;对于一个信号对应两个反应槽的连接来说,一秒钟可以调用一百二十万次。这个速度是不经过连接而直接进行回调的速度的十分之一。请注意这里的十分之一速度比是调用速度的比较,而不是一个完整函数执行时间的比较。事实上一般情况下一个函数的总执行时间大部分是在执行部分,只有小部分是在调用部分,因些这个速度是可以接受的。这就象面向对象的编程和早些年的结构化编程相比一样:
程序的执行效率并没有提高,反而是有所下降的,但现在大家都在用面向对象的方法编写程序。用一部分执行效率换回开发效率与维护效率是值得的,况且现在已是P4为主流的时代。
我们先来看一个简单的样例:
在以上程序中,一旦信号与反应槽连接,当执行a.setValue(79)时就会释放出一个valueChanged(int)的信号,对象b将会收到这个信号并触发setValue(int)这个函数。当b在执行setValue(int)这个函数时,它也将释放valueChanged(int)这个信号,当然b的信号无人接收,因此就什么也没干。请注意,在样例中我们仅当输入变量v不等于val时才释放信号,因此就算对a与b进行了交叉连接也不会导致死循环的发生。由于在样例中使用了Qt特有的关键字和宏,而Qt本身并不包括C++的编译器,因此如果用流行的编译程序(如Windows下的Visual C++或Linux下的gcc)是不能直接编译这段代码的,必须用Qt的中间编译工具moc.exe把该段代码转换为无专用关键字和宏的C++代码才能为这些编译程序所解析、编译与链接。
以上代码中信号与反应槽的定义是在类中实现的。那么,非类成员的函数,比如说一个全局函数可不可以也这样做呢?答案是不行,只有是自身定义了信号的类或其子类才可以发出该种信号。一个对象的不同信号可以连接至不同的对象。当一个信号被释放时,与之连接的反应槽将被立刻执行,就象是在程序中直接调用该函数一样。信号的释放过程是阻塞的,这意味着只有当反应槽执行完毕后该信号释放过程才返回。如果一个信号与多个反应槽连接,则这些反应槽将被顺序执行,排序过程则是任意的。因此如果程序中对这些反应槽的先后执行次序有严格要求的,应特别注意。使用信号时还应注意:信号的定义过程是在类的定义过程即头文件中实现的。为了中间编译工具moc的正常运行,不要在源文件(.cpp)中定义信号,同时信号本身不应返回任何数据类型,即是空值(void)。如果你要设计一个通用的类或控件,则在信号或反应槽的参数中应尽可能使用常规数据以增加通用性。如上例代码中valueChanged的参数为int型,如果它使用了特殊类型如QRangeControl::Range,那么这种信号只能与RangeControl中的反应槽连接。如前所述,反应槽也是常规函数,与未定义slots的用户函数在执行上没有任何区别。
但在程序中不可把信号与常规函数连接在一起,否则信号的释放不会引起对应函数的执行。要命的是中间编译程序moc并不会对此种情况报错,C++编译程序更不会报错。初学者比较容易忽略这一点,往往是程序编好了没有错误,逻辑上也正确,但运行时就是不按自己的意愿出现结果,这时候应检查一下是不是这方面的疏忽。
Qt的设计者之所以要这样做估计是为了信号与反应槽之间匹配的严格性。既然反应槽与常规函数在执行时没有什么区别,因此它也可以定义成公共反应槽(public slots)、保护反应槽(protected slots)和私有反应槽(private slots)。如果需要,我们也可以把反应槽定义成虚函数以便子类进行不同的实现,这一点是非常有用的。
只讨论一下信号与反应槽的使用好象还不过瘾,既然Qt的X11 Free版提供了源代码,我们就进去看一下在QObject中connect的实现。由于Qt是一个跨平台的开发库,为了与不同平台上的编译器配合,它定义了一个中间类QMetaObject,该类的作用是存放有关信号/反应槽以及对象自身的信息。这个类是Qt内部使用的,用户不应去使用它。
再看一下QObject中connect的实现。剥去粗枝,函数中便露出一个更细化的函数:connectInternal,它又做了哪些工作呢?让我们看一下:
到此,信号与反应槽的连接已建立完毕,那么信号产生时又是如何触发反应槽的呢?从QObject的定义中可以看出其有多个activate_signal的成员函数,这些函数都是protected的,也即只有其自身或子类才可以使用。看一下它的实现:
至此我们已经可以基本了解Qt中信号/反应槽的流程。我们再看一下Qt为此而新增的语法:三个关键字:slots、signals和emit,三个宏:SLOT()、SIGNAL()和Q_OBJECT。在头文件qobjectdefs.h中,我们可以看到这些新增语法的定义如下:
由此可知其实三个关键字没有做什么事情,而SLOT()和SIGNAL()宏也只是在字符串前面简单地加上单个字符,以便程序仅从名称就可以分辨谁是信号、谁是反应槽。中间编译程序moc.exe则可以根据这些关键字和宏对相应的函数进行'翻译',以便在C++编译器中编译。剩下一个宏Q_OBJECT比较复杂,它的定义如下:
从定义中可以看出该宏的作用有两个:一是对与自己相关的QMetaObject中间类操作进行声明,另一个是对信号的释放操作和反应槽的激活操作进行声明。当moc.exe对头文件进行预编译之后,将会产生一个可供C++编译器编译的源文件。以上述的Demo类为例,假设它的代码文件分别为demo.h和demo.cpp,预编译后将产生moc_demo.cpp,其主要内容如下:
该文件中既没有Qt特有的关键字,也没有特殊的宏定义,完全符合普通的C++语法,因此可以顺利编译和链接。
posted @ 2016-12-22 17:44FelixWang 阅读(71186) 评论(0) 编辑收藏

How often is a an object copied, if it is emitted by a signal as a const reference and received by a slot as a const reference? How does the behaviour differ for direct and queued signal-slot connections? What changes if we emit the object by value or receive it by value?
Nearly every customer asks this question at some point in a project. The Qt documentation doesn't say a word about it. There is a good discussion on stackoverflow, which unfortunately leaves it to the reader to pick the right answer from all the answers and comments. So, let's have a systematic and detailed look at how arguments are passed to signals and slots.

Setting the Stage

Slots

For our experiments, we need a copyable class that we will pass by const reference or by value to signals and slots. The class – let's call it Copy – looks as follows.

The copy constructor and the assignment operator simply perform a member-wise copy – like the compiler generated versions would do. We implement them explicitly to set breakpoints or to print debugging messages. The default constructor is only required for queued connections. We'll learn the reason later.
We need another class, MainView, which ultimately derives from QObject. MainView provides the following signals and slots.

Signal

MainView provides four signal-slot connections for each connection type.

The above code is used for direct connections. For queued connections, we comment out the first line and uncomment the second and third line.
The code for emitting the signals looks as follows:

Direct Connections

sendConstRef => receiveConstRef

We best set breakpoints in the copy constructor and assignment operator of the Copy class. If our program only calls emit sendConstRef(c), the breakpoints are not hit at all. So, no copies happen. Why?
The result is not really surprising, because this is exactly how passing arguments as const references in C++ works and because a direct signal-slot connection is nothing else but a chain of synchronous or direct C++ function calls.
Nevertheless, it is instructive to look at the chain of function calls executed when the sendConstRef signal is emitted.

The meta-object code of steps 2, 3 and 4 – for marshalling the arguments of a signal, routing the emitted signal to the connected slots and de-marshalling the arguments for the slot, respectively – is written in such a way that no copying of the arguments occurs. This leaves us with two places, where copying of a Copy object could potentially occur: when passing the Copy object to the functions MainView::sendConstRef or MainView::receiveConstRef.
These two places are governed by standard C++ behaviour. Copying is not needed, because both functions take their arguments as const references. There are also no life-time issues for the Copy object, because receiveConstRef returns before the Copy object goes out of scope at the end of sendConstRef.

sendConstRef => receiveValue

Based on the detailed analysis in the last section, we can easily figure out that only one copy is needed in this scenario. When qt_static_meta_call calls receiveValue(Copy c) in step 4, the original Copy object is passed by value and hence must be copied.

sendValue => receiveConstRef

Emit

What the code below does is to create a slot and signal with a function that will emit a signal when called (the function that is) so the signal is 'fired' off to what ever is listening to it, to connect a signal to a slot you use the QObject::connect as below. In Qt, we have an alternative to the callback technique: We use signals and slots. A signal is emitted when a particular event occurs. Qt's widgets have many predefined signals, but we can always subclass widgets to add our own signals to them. A slot is a function that is called in response to a particular signal. Signal and Slots A signal is emitted when a particular event occurs. Qt Jambi's widgets have many predefined signals, but we can always subclass widgets to add our own signals to them. A slot is a method that is called in response to a particular signal. How does the behaviour differ for direct and queued signal-slot connections? What changes if we emit the object by value or receive it by value? Nearly every customer asks this question at some point in a project. The Qt documentation doesn't say a word about it.

Qt中的类库有接近一半是从基类QObject上继承下来,信号与反应槽(signals/slot)机制就是用来在QObject类或其子类间通讯的方法。作为一种通用的处理机制,信号与反应槽非常灵活,可以携带任意数量的参数,参数的类型也由用户自定。同时其本身也是类型安全的,任何一个从QObject或其子类继承的用户类都可以使用信号与反应槽。
信号的作用如同Windows系统中的消息。在Qt中,对于发出信号的对象来说,它并不知道是谁接收了这个信号。这样的设计可能在某些地方会有些不便,但却杜绝了紧耦合,于总体设计有利。反应槽是用来接收信号的, 但它实际上也是普通的函数,程序员可以象调用普通函数一样来调用反应槽。与信号类似的是,反应槽的拥有者也不知道是谁向它发出了信号。在程序设计过程中,多个信号可以连接至一个反应槽,类似的,一个信号也可以连接至多个反应槽,甚至一个信号可以连接至另一个信号。
在Windows中,如果我们需要多个菜单都激发一个函数,一般是先写一个共用函数,然后在每个菜单的事件中调用此函数。在Qt中如果要实现同样的功能,就可以把实现部分写在一个菜单中,然后把其他菜单与这个菜单级联起来。
虽然信号/反应槽机制有很多优点,使用也很方便,但它也不是没有缺点。最大的缺点在于要稍微牺牲一点性能。根据Trolltech公司的自测,在CPU为Intel PentiumII 500 Mhz的PC机上,对于一个信号对应一个反应槽的连接来说,一秒钟可以调用两百万次;对于一个信号对应两个反应槽的连接来说,一秒钟可以调用一百二十万次。这个速度是不经过连接而直接进行回调的速度的十分之一。请注意这里的十分之一速度比是调用速度的比较,而不是一个完整函数执行时间的比较。事实上一般情况下一个函数的总执行时间大部分是在执行部分,只有小部分是在调用部分,因些这个速度是可以接受的。这就象面向对象的编程和早些年的结构化编程相比一样:
程序的执行效率并没有提高,反而是有所下降的,但现在大家都在用面向对象的方法编写程序。用一部分执行效率换回开发效率与维护效率是值得的,况且现在已是P4为主流的时代。
我们先来看一个简单的样例:
在以上程序中,一旦信号与反应槽连接,当执行a.setValue(79)时就会释放出一个valueChanged(int)的信号,对象b将会收到这个信号并触发setValue(int)这个函数。当b在执行setValue(int)这个函数时,它也将释放valueChanged(int)这个信号,当然b的信号无人接收,因此就什么也没干。请注意,在样例中我们仅当输入变量v不等于val时才释放信号,因此就算对a与b进行了交叉连接也不会导致死循环的发生。由于在样例中使用了Qt特有的关键字和宏,而Qt本身并不包括C++的编译器,因此如果用流行的编译程序(如Windows下的Visual C++或Linux下的gcc)是不能直接编译这段代码的,必须用Qt的中间编译工具moc.exe把该段代码转换为无专用关键字和宏的C++代码才能为这些编译程序所解析、编译与链接。
以上代码中信号与反应槽的定义是在类中实现的。那么,非类成员的函数,比如说一个全局函数可不可以也这样做呢?答案是不行,只有是自身定义了信号的类或其子类才可以发出该种信号。一个对象的不同信号可以连接至不同的对象。当一个信号被释放时,与之连接的反应槽将被立刻执行,就象是在程序中直接调用该函数一样。信号的释放过程是阻塞的,这意味着只有当反应槽执行完毕后该信号释放过程才返回。如果一个信号与多个反应槽连接,则这些反应槽将被顺序执行,排序过程则是任意的。因此如果程序中对这些反应槽的先后执行次序有严格要求的,应特别注意。使用信号时还应注意:信号的定义过程是在类的定义过程即头文件中实现的。为了中间编译工具moc的正常运行,不要在源文件(.cpp)中定义信号,同时信号本身不应返回任何数据类型,即是空值(void)。如果你要设计一个通用的类或控件,则在信号或反应槽的参数中应尽可能使用常规数据以增加通用性。如上例代码中valueChanged的参数为int型,如果它使用了特殊类型如QRangeControl::Range,那么这种信号只能与RangeControl中的反应槽连接。如前所述,反应槽也是常规函数,与未定义slots的用户函数在执行上没有任何区别。
但在程序中不可把信号与常规函数连接在一起,否则信号的释放不会引起对应函数的执行。要命的是中间编译程序moc并不会对此种情况报错,C++编译程序更不会报错。初学者比较容易忽略这一点,往往是程序编好了没有错误,逻辑上也正确,但运行时就是不按自己的意愿出现结果,这时候应检查一下是不是这方面的疏忽。
Qt的设计者之所以要这样做估计是为了信号与反应槽之间匹配的严格性。既然反应槽与常规函数在执行时没有什么区别,因此它也可以定义成公共反应槽(public slots)、保护反应槽(protected slots)和私有反应槽(private slots)。如果需要,我们也可以把反应槽定义成虚函数以便子类进行不同的实现,这一点是非常有用的。
只讨论一下信号与反应槽的使用好象还不过瘾,既然Qt的X11 Free版提供了源代码,我们就进去看一下在QObject中connect的实现。由于Qt是一个跨平台的开发库,为了与不同平台上的编译器配合,它定义了一个中间类QMetaObject,该类的作用是存放有关信号/反应槽以及对象自身的信息。这个类是Qt内部使用的,用户不应去使用它。
再看一下QObject中connect的实现。剥去粗枝,函数中便露出一个更细化的函数:connectInternal,它又做了哪些工作呢?让我们看一下:
到此,信号与反应槽的连接已建立完毕,那么信号产生时又是如何触发反应槽的呢?从QObject的定义中可以看出其有多个activate_signal的成员函数,这些函数都是protected的,也即只有其自身或子类才可以使用。看一下它的实现:
至此我们已经可以基本了解Qt中信号/反应槽的流程。我们再看一下Qt为此而新增的语法:三个关键字:slots、signals和emit,三个宏:SLOT()、SIGNAL()和Q_OBJECT。在头文件qobjectdefs.h中,我们可以看到这些新增语法的定义如下:
由此可知其实三个关键字没有做什么事情,而SLOT()和SIGNAL()宏也只是在字符串前面简单地加上单个字符,以便程序仅从名称就可以分辨谁是信号、谁是反应槽。中间编译程序moc.exe则可以根据这些关键字和宏对相应的函数进行'翻译',以便在C++编译器中编译。剩下一个宏Q_OBJECT比较复杂,它的定义如下:
从定义中可以看出该宏的作用有两个:一是对与自己相关的QMetaObject中间类操作进行声明,另一个是对信号的释放操作和反应槽的激活操作进行声明。当moc.exe对头文件进行预编译之后,将会产生一个可供C++编译器编译的源文件。以上述的Demo类为例,假设它的代码文件分别为demo.h和demo.cpp,预编译后将产生moc_demo.cpp,其主要内容如下:
该文件中既没有Qt特有的关键字,也没有特殊的宏定义,完全符合普通的C++语法,因此可以顺利编译和链接。
posted @ 2016-12-22 17:44FelixWang 阅读(71186) 评论(0) 编辑收藏

How often is a an object copied, if it is emitted by a signal as a const reference and received by a slot as a const reference? How does the behaviour differ for direct and queued signal-slot connections? What changes if we emit the object by value or receive it by value?
Nearly every customer asks this question at some point in a project. The Qt documentation doesn't say a word about it. There is a good discussion on stackoverflow, which unfortunately leaves it to the reader to pick the right answer from all the answers and comments. So, let's have a systematic and detailed look at how arguments are passed to signals and slots.

Setting the Stage

For our experiments, we need a copyable class that we will pass by const reference or by value to signals and slots. The class – let's call it Copy – looks as follows.

The copy constructor and the assignment operator simply perform a member-wise copy – like the compiler generated versions would do. We implement them explicitly to set breakpoints or to print debugging messages. The default constructor is only required for queued connections. We'll learn the reason later.
We need another class, MainView, which ultimately derives from QObject. MainView provides the following signals and slots.

MainView provides four signal-slot connections for each connection type.

The above code is used for direct connections. For queued connections, we comment out the first line and uncomment the second and third line.
The code for emitting the signals looks as follows:

Direct Connections

sendConstRef => receiveConstRef

We best set breakpoints in the copy constructor and assignment operator of the Copy class. If our program only calls emit sendConstRef(c), the breakpoints are not hit at all. So, no copies happen. Why?
The result is not really surprising, because this is exactly how passing arguments as const references in C++ works and because a direct signal-slot connection is nothing else but a chain of synchronous or direct C++ function calls.
Nevertheless, it is instructive to look at the chain of function calls executed when the sendConstRef signal is emitted.

The meta-object code of steps 2, 3 and 4 – for marshalling the arguments of a signal, routing the emitted signal to the connected slots and de-marshalling the arguments for the slot, respectively – is written in such a way that no copying of the arguments occurs. This leaves us with two places, where copying of a Copy object could potentially occur: when passing the Copy object to the functions MainView::sendConstRef or MainView::receiveConstRef.
These two places are governed by standard C++ behaviour. Copying is not needed, because both functions take their arguments as const references. There are also no life-time issues for the Copy object, because receiveConstRef returns before the Copy object goes out of scope at the end of sendConstRef.

sendConstRef => receiveValue

Based on the detailed analysis in the last section, we can easily figure out that only one copy is needed in this scenario. When qt_static_meta_call calls receiveValue(Copy c) in step 4, the original Copy object is passed by value and hence must be copied.

sendValue => receiveConstRef

One copy happens, when the Copy object is passed by value to sendValue by value.

sendValue => receiveValue

This is the worst case. Two copies happen, one when the Copy object is passed to sendValue by value and another one when the Copy object is passed to receiveValue by value.

Queued Connections

A queued signal-slot connection is nothing else but an asynchronous function call. Conceptually, the routing function QMetaObject::activate does not call the slot directly any more, but creates a command object from the slot and its arguments and inserts this command object into the event queue. When it is the command object's turn, the dispatcher of the event loop will remove the command object from the queue and execute it by calling the slot.
When QMetaObject::activate creates the command object, it stores a copy of the Copy object in the command object. Therefore, we have one extra copy for every signal-slot combination.
We must register the Copy class with Qt's meta-object system with the command qRegisterMetaType('Copy'); in order to make the routing of QMetaObject::activate work. Any meta type is required to have a public default constructor, copy constructor and destructor. That's why Copy has a default constructor.
Queued connections do not only work for situations where the sender of the signal and the receiver of the signal are in the same thread, but also when the sender and receiver are in different threads. Even in a multi-threaded scenario, we should pass arguments to signals and slots by const reference to avoid unnecessary copying of the arguments. Qt makes sure that the arguments are copied before they cross any thread boundaries.

Qt Signals Slots Emit Waves

Conclusion

The following table summarises our results. The first line, for example, reads as follows: If the program passes the argument by const reference to the signal and also by const reference to the slot, there are no copies for a direct connection and one copy for a queued connection.

SignalSlotDirectQueued
const Copy&const Copy&01
const Copy&Copy12
Copyconst Copy&12
CopyCopy23

Qt Signals Slots Emit In The Dark

The conclusion from the above results is that we should pass arguments to signals and slots by const reference and not by value. This advice is true for both direct and queued connections. Even if the sender of the signal and the receiver of the slot are in different threads, we should still pass arguments by const reference. Qt takes care of copying the arguments, before they cross the thread boundaries – and everything is fine.
By the way, it doesn't matter whether we specify the argument in a connect call as const Copy& or Copy. Qt normalises the type to Copy any way. This normalisation does not imply, however, that arguments of signals and slots are always copied – no matter whether they are passed by const reference or by value.





broken image