信号槽的语法糖

Syntax sugar for signal slot

本文关键字:语法 信号      更新时间:2023-10-16

最近我在互联网上搜索了一个好的信号槽库,想知道为什么我们需要如此繁琐的语法来将成员方法连接到信号?

通常我们必须写这样的东西:

mySignal.connect(&MyClassName::myMethodName, this);

或者类似的:

mySignal += std::bind(&MyClassName::myMethodName, this, std::placeholders::_1);

有明显的重复和不必要的打字。在现代C++中,是否有可能以C#方式实现这样的功能:

mySignal += myMethodName

并自动捕获指向成员函数的指针和上下文中的指针?

在现代C++中,有可能以C#的方式实现这样的功能吗?[…]

不,这在C++中是不可能的。获取成员函数地址的语法要求用类名(即&MyClassName::myMethodName)限定函数名。

如果您不想指定类名,一种可能性是使用lambdas。特别是,如果你能负担得起C++14编译器,通用lambdas允许写:

mySignal.connect([this] (auto x) -> { myMethodName(x) });

遗憾的是,你再也找不到比这更简洁的了。您可以使用默认的lambda捕获来保存一些语法噪音:

mySignal.connect([&] (auto x) -> { myMethodName(x) });

然而,Scott Meyers在他的新书《高效现代C++》中警告了默认lambda捕获模式的陷阱。从可读性的角度来看,与第一个选项相比,我不确定这是否能改善很多。

此外,如果您希望lambda将其参数完美地转发给myMethodName:,事情很快就会变得尴尬

mySignal.connect([&] (auto&& x) -> { myMethodName(std::forward<decltype(x)>(x)) });

如果你不介意宏(我通常会介意),你可以按照Quentin在回答中的建议,使用基于预处理器的解决方案。然而,在这种情况下,我更喜欢使用完美的转发lambda:

#define SLOT(name) 
        [this] (auto&&... args) { name (std::forward<decltype(args)>(args)...); }

你可以这样使用:

e.connect(SLOT(foo));

以下是Coliru上的现场演示。

预处理器应该尝试一下吗?

#include <type_traits>
#define CONNECT(method) 
    connect(&std::remove_pointer_t<decltype(this)>::method, this)

甘蔗的用途如下:

mySignal.CONNECT(myMethodName);