新手学习C++,我正在阅读《深入了解信号和槽》,它声称:1)回调本质上是不安全的类型,2)为了使它们安全,您需要在函数周围定义一个纯虚拟类包装器。我很难理解为什么会这样。作为例子,这是Qt在其信号和槽教程页面上提供的代码:
// Header file
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
// .cpp file
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}
// Later on...
Counter a, b;
QObject::connect(&a, SIGNAL(valueChanged(int)),
&b, SLOT(setValue(int)));
a.setValue(12); // a.value() == 12, b.value() == 12
b.setValue(48); // a.value() == 12, b.value() == 48
以下是使用回调函数重写的代码:
#include <functional>
#include <vector>
class Counter
{
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
std::vector<std::function<void(int)>> valueChanged;
void setValue(int value);
private:
int m_value;
};
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
for (auto func : valueChanged) {
func(value);
}
}
}
// Later on...
Counter a, b;
auto lambda = [&](int value) { b.setValue(value); };
a.valueChanged.push_back(lambda);
a.setValue(12);
b.setValue(48);
正如您所看到的,回调版本比Qt版本更加类型安全且更短,尽管他们声称它不是这样。除了 Counter
之外,它不定义任何新类。它仅使用标准库代码,不需要特殊编译器(moc)即可工作。那么,为什么信号和槽比回调函数更受欢迎呢?C++11是否已经使这些概念过时了?
谢谢。
shared_ptr
、lambda表达式、闭包和std::function
等功能,采用基于标准C++概念的信号和槽实现可能会更好、更简单(减少模板代码、增加灵活性、减少对元对象编译系统(MOC)的依赖)。但在Qt最初的设计时期并非如此。 - user4842163void*
,然后当调用你的函数指针时,它可能具有类似于void (*event)(void* user_data)
的签名。然后,你需要将user_data
转换为之前传递的内容(你想要访问的应用程序数据)。 - user4842163int
的签名。但问题是你的 lambda 是闭包,它们访问这些Counters
。为了设计一个通用的 API,在 C 风格的函数指针回调中可以访问这些计数器或其他任何东西,它们通常必须通过 void 指针压缩,或者你必须完全失去通用性,并设计一个仅在触发事件时使用计数器的 API。要么你把你的计数器变成全局变量。希望这样说得清楚! - user4842163