一、说明
学习一个技术的最经典的方式是什么?就是重复造轮子。大到国家小到个人,基本都是这样,先引进消化,吸收后才能够慢慢进行创新。所谓引进消化,其实就是自己造轮子,只有造好轮子,真正掌握其中的技术原理和细节,才能够"知其所以然"。
在前面分析了很多有名气的开源软件框架的源码,其实也是这个意思。从底层掌握设计、开发的全流程,然后才能开始用其优秀之处进行自己的实际应用。在实际中融会贯通的理解优秀的底层实现逻辑后,才能够据此进行更多更好的创新。
二、Qt的信号-槽机制
Qt作为一个在C++中的优秀平台,广泛应用于GUI和跨平台的开发。在Qt中,其基础的信号和槽机制是GUI开发的重要一环。信号-槽机制本质是一种实现了对类型安全、对象生命周期管理、跨线程派发的观察者机制,其表现形式仍然是函数。在Qt中,为了实现的方便,提供了MOC的元编程机制(Q_OBJECT);为了匹配的适应性,也提供了多种的连接类型。Qt提供了对信号槽机制的安全保证即生命周期的安全控制管理。在跨线程应用时,只要小心使用,也是没有大问题的。
需要注意的是,在新的支持Lambda表达式的机制中,要小心生命周期的不匹配。信号机制决定了它在跨线程高频使用时可能会遇到一些问题,如队列的堆积、处理的效率等。特别是大数据的传递,最好采用指针或共享数据(可能需要锁)。
那么Qt中的信号和槽到底是什么呢?信号和槽,Signals and Slots。是一种高级的回调机制,它提供了对象之间的通信方式并提高了代码的可读性和维护性。信号和槽其实誻一个发送者对象(Sender)通过发射(Emitting)一个信号(Signal)来通知另一个接收者对象(Receiver)某个事件的发生。它们之间通过槽(Slot)来实现对事件的响应。
其基本的流程如下:
-
声明信号
即声明信号函数,一般如下:
csignals: void dataChanged(int d); -
声明槽
编写槽的函数,一般如下:
c++public slots: void onDataChanged(int d); -
建立连接
将信号和槽建立联系,如下方法:
cconnect(sender, &Sender::dataChanged, receiver, &Receiver::onDataChanged); -
发送信号
真正的触发事件,其方式基本为:
cemit valueChanged(1);
当然在细节实现上,还有很多的相关技术点,如MOC机制、反射及跨线程应用等等,有兴趣可以自行查看,但此处不是重点。
三、C++的实现
有Qt的实现在前,那么如果用C++自己怎么实现呢?首先要想一下Qt实现的方式,自己如何才能够与其达到一致。
- 创建函数以及函数的传递需要函数对象std::function
- 信号和槽函数的处理需要std::bind
- 参数转发处理可能需要std::forward
- 参数及对象控制需要智能指针
- 如果再复杂一些需要支持Lambda表达式
- 模板技术特别是变参模板
这可以算是一个相对简单实现的流程,它不需要MOC和反射,也不需要支持连接类型等,只从原理上实现相关的信号槽机制。明白了相关实现需要的技术,下面看一个最基础的实现:
c
#include <iostream>
#include <vector>
#include <functional>
class Signal {
public:
void connect(std::function<void()> sf) {
slotFuncs.push_back(sf);
}
void emit() {
for (auto& sf : slotFuncs) sf();
}
private:
std::vector<std::function<void()>> slotFuncs;
};
int main() {
Signal btnClick;
btnClick.connect([]() { std::cout << "call first slot function!"<<std::endl; });
btnClick.connect([]() {
std::cout << "call second slot function!"<<std::endl; });
btnClick.emit();
}
看到上面的代码会不会想用一个map存储槽对象,传参怎么办?如何处理变参?是不是和刚刚提到的技术应用对应上了。
四、例程
上面的基础实现,其实是提供了一个复刻Qt信号槽机制的入门方式,即造一个长得有点像的最简单轮子。下面再看一个相对复杂的实现:
c
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
template <typename... Args> class Signal {
public:
using Slot = std::function<void(Args...)>;
size_t connect(Slot slot) {
std::lock_guard<std::mutex> lock(mutex_);
size_t id = nextId_++;
slots_[id] = std::move(slot);
return id;
}
template <typename T> size_t connect(T *obj, void (T::*method)(Args...)) {
return connect([obj, method](Args... args) { (obj->*method)(args...); });
}
void disconnect(size_t id) {
std::lock_guard<std::mutex> lock(mutex_);
slots_.erase(id);
}
void emit(Args... args) {
std::vector<Slot> copiedSlots;
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto &[id, slot] : slots_) {
copiedSlots.push_back(slot);
}
}
for (auto &slot : copiedSlots) {
slot(args...);
}
}
void operator()(Args... args) { emit(args...); }
private:
std::mutex mutex_;
std::unordered_map<size_t, Slot> slots_;
size_t nextId_ = 1;
};
class Button {
public:
Signal<> clicked;
void click() {
std::cout << "button clicked" << std::endl;
clicked.emit();
}
};
class Slider {
public:
Signal<int> valueChanged;
void setValue(int v) {
if (value_ == v) {
return;
}
value_ = v;
valueChanged.emit(value_);
}
private:
int value_ = 0;
};
class Label {
public:
void setTextFromClick() { std::cout << "set lbael clicked" << std::endl; }
void setNumber(int value) { std::cout << " value = " << value << std::endl; }
};
int main() {
Button btn;
Slider slider;
Label lab;
auto connClick = btn.clicked.connect(&lab, &Label::setTextFromClick);
auto connSet = slider.valueChanged.connect(&lab, &Label::setNumber);
auto connLambda = slider.valueChanged.connect([](int value) {
std::cout << "slot received value: " << value << std::endl;
});
btn.click();
slider.setValue(11);
slider.setValue(22);
std::cout << "disconnect lambda slot" << std::endl;
slider.valueChanged.disconnect(connLambda);
slider.setValue(23);
std::cout << "disconnect button slot" << std::endl;
btn.clicked.disconnect(connClick);
btn.click();
return 0;
}
当然,上面的代码照Qt的信号槽差得还很远,但这就入门了。下来只需要按照自己的需求不断的完善即可。掌握了建造方式,那么剩下的就只是不断的在功能、安全和接口上不断的下功夫了。
五、总结
常说的站在巨人的肩膀上,不是简单的跳上去。否则的话,巨人不高兴还会赶人下来的。跳上去后,看到更远处的风景,就要学会自己变成巨人。如此迭代,人类社会就是么进步。同样,技术也是如此。