AUI Framework  master
Cross-platform module-based framework for developing C++20 desktop applications
Signal-slot

Signal-slots is an object messaging mechanism that creates seamless relations between objects. More...

Detailed Description

Signal-slots is an object messaging mechanism that creates seamless relations between objects.

Signal-slots were originally implemented in Qt and it has proven themselves as an object messaging mechanism making it easy to implement the observer pattern without boilerplate code. Signal-slot tracks object existence on the both ends so destruction of either sender or receiver object breaks the link between them. When the sender or receiver object is destroyed slot execution is never possible even in a multithreaded environment. Almost any signal can be connected to almost any slot (even lambda) by any code.

All classes that inherit from AObject or one of its subclasses (e.g., AView) can contain signals and slots. Signals are emitted by objects when they change their state in a way that may be interesting to other objects. This is all the object does to communicate. It does not know or care whether anything is receiving the signals it emits. This is true information encapsulation, and ensures that the object can be used as a software component.

Signal declarations are special public fields of any class that inherit from AObject.

class Counter: public AObject {
public:
Counter() = default;
void setValue(int value) {
mValue = value;
emit valueChanged;
}
signals:
emits<> valueChanged;
private:
int mValue = 0;
};
A base object class.
Definition: AObject.h:49
#define emit
emits the specified signal in context of this object.
Definition: AObject.h:196

They can specify arguments:

emits<int> valueChanged;
...
emit valueChanged(mValue);

Any member function of a class can be used as a slot.

You can connect as many signals as you want to a single slot, and a signal can be connected to as many slots as you need.

Signals

Signals are publicly accessible fields that notify an object's client when its internal state has changed in some way that might be interesting or significant. These signals can be emitted from various locations, but it is generally recommended to only emit them from within the class that defines the signal and its subclasses.

The slots connected to a signal are generaly executed immediately when the signal is emitted like a regular function call. emit returns control after all slots are called. If receiver has AObject::setSlotsCallsOnlyOnMyThread set to true and emit was called on a different thread, emit queues slot execution to the AEventLoop associated with receiver's thread. All AView have this behaviour enabled by default.

Slots

A slot is triggered by the emission of a related signal. Slot is no more than a regular class method with a signal connected to it.

When called directly, slots follow standard C++ rules and syntax. However, through a signal-slot connection, any component can invoke a slot regardless of its accessibility level. Visibility scope matters only when creating connection (AObject::connect). This means a signal from one class can call a private slot in another unrelated class if connection is created within class itself (with access to its private/protected methods).

Furthermore, slots can be defined as virtual functions, which have been found to be beneficial in practical application development.

Compared to callbacks, the signals and slots system has some performance overhead due to its increased flexibility. The difference is typically insignificant for real-world applications. In general, emitting a signal connected to various slots can result in an execution time roughly ten times slower than direct function calls. This delay comes from locating the connection object and safely iterating over connections.

The trade-off between the signals and slots mechanism's simplicity and flexibility compared to pure function calls or callbacks is well-justified for most applications, given its minimal performance cost that users won't perceive.

Basic example

Let's use Counter from our previous example:

class Counter: public AObject {
public:
Counter() = default;
void setValue(int value) {
mValue = value;
emit valueChanged(value);
}
signals:
emits<int> valueChanged;
private:
int mValue = 0;
};
class MyApp: public AObject {
public:
MyApp() {}
void run() {
connect(mCounter->valueChanged, this, &MyApp::printCounter);
mCounter->setValue(123); // prints "New value: 123"
}
private:
_<Counter> mCounter = _new<Counter>();
void printCounter(int value) {
ALogger::info("MyApp") << value;
}
};
auto app = _new<MyApp>();
app->run();
return 0;
}
An std::weak_ptr with AUI extensions.
Definition: SharedPtrTypes.h:177
#define AUI_ENTRY
Application entry point.
Definition: Entry.h:92

If connect(mCounter->valueChanged, this, &MyApp::printCounter); looks too long for you, you can use slot macro:

connect(mCounter->valueChanged, slot(this)::printCounter);
#define slot(v)
Passes some variable and type of the variable separated by comma. It's convenient to use with the con...
Definition: kAUI.h:90

Furthermore, when connecting to this, slot(this) can be replaced with me:

connect(mCounter->valueChanged, me::printCounter);

Lambda can be used as a slot either:

connect(mCounter->valueChanged, this, [](int value) {
ALogger::info("MyApp") << value;
});

As with methods, this can be omitted:

connect(mCounter->valueChanged, [](int value) {
ALogger::info("MyApp") << value;
});

UI example

Knowing basics of signal slots, you can now utilize UI signals:

mOkButton = _new<AButton>("OK");
...
connect(mOkButton->clicked, [] { // the signal is connected to "this" object
ALogger::info("Example") << "The button was clicked";
});
Note
In lambda, do not capture shared pointer (AUI's _) of signal emitter or receiver object by value. This would cause a memory leak:
mOkButton = _new<AButton>("OK");
...
connect(mOkButton->clicked, [view] { // WRONG!!!
view->setText("clicked");
});
Do this way:
mOkButton = _new<AButton>("OK");
...
connect(mOkButton->clicked, [view = view.get()] { // ok
view->setText("clicked");
});

Going further

Let's take our previous example with Counter and make an UI app. Signal slot reveals it's power when your objects have small handy functions, so lets add increase method to our counter:

class Counter: public AObject {
public:
Counter() = default;
void setValue(int value) {
mValue = value;
emit valueChanged(value);
}
void increase() {
setValue(mCounter + 1);
}
signals:
emits<int> valueChanged;
private:
int mValue = 0;
};
class MyApp: public AWindow {
public:
MyApp() {
auto label = _new<ALabel>("-");
auto button = _new<AButton>("Increase");
using namespace declarative;
setContents(Vertical {
label,
button,
});
connect(button->clicked, slot(mCounter)::increase); // beauty, huh?
connect(mCounter->valueChanged, label, [label = label.get()](int value) {
label->setText("{}"_format(value));
});
}
private:
_<Counter> mCounter = _new<Counter>();
};
auto app = _new<MyApp>();
app->show();
return 0;
}
Represents a window in the underlying windowing system.
Definition: AWindow.h:45

This way, by clicking on "Increase", it would increase the counter and immediately display value via label.

Let's make things more declarative and use let syntax to set up connections:

MyApp() {
using namespace declarative;
setContents(Vertical {
_new<ALabel>("-") let {
connect(counter->valueChanged, label, [label = it.get()](int value) {
label->setText("{}"_format(value));
});
},
_new<AButton>("Increase") let {
connect(it->clicked, slot(mCounter)::increase);
},
});
}
#define let
Performs multiple operations on a single object without repeating its name (in place) This function c...
Definition: kAUI.h:262

See also ADataBinding for making reactive UI's on trivial data.

Arguments

If signal declares arguments (i.e, like AView::keyPressed), you can accept them:

view = _new<ATextField>();
...
connect(view->keyPressed, [](AInput::Key k) { // the signal is connected to "this" object
ALogger::info("Example") << "Key was pressed: " << k;
});

The signals and slots mechanism is type safe: The signature of a signal must match the signature of the receiving slot. Also, a slot may have a shorter signature than the signal it receives because it can ignore extra arguments:

view = _new<ATextField>();
...
connect(view->keyPressed, [] { // the signal is connected to "this" object
ALogger::info("Example") << "Key was pressed";
});

Differences between Qt and AUI implementation

Suppose we want to emit statusChanged signal with a string argument and connect it with showMessage slot:

Qt AUI
Signal declaration
signals:
void statusChanged(QString str);
signals:
emits<AString> statusChanged;
Slot declaration
slots:
void showMessage(QString str);
void showMessage(AString str);
Represents a Unicode character string.
Definition: AString.h:37
Connect from this to this
connect(this, SIGNAL(statusChanged(QString), this, SLOT(showMessage(QString)));
connect(statusChanged, me::showMessage);
Connect from the emitter object to the sender object
QObject::connect(emitter, SIGNAL(statusChanged(QString), receiver, SLOT(showMessage(QString)));
AObject::connect(emitter->statusChanged, slot(receiver)::showMessage);

Classes

class  AAbstractSignal
 Base class for signal. More...
 
class  AObject
 A base object class. More...
 

Macros

#define emit   (*this) ^
 emits the specified signal in context of this object. More...
 
#define AUI_EMIT_FOREIGN(object, signal, ...)   (*object) ^ object->signal(__VA_ARGS__)
 emits the specified signal in context of specified object. More...
 
#define me   this, &std::remove_reference_t<decltype(*this)>
 Passes the current class and type of the current class separated by comma. It's convenient to use with the connect function: More...
 
#define slot(v)   v, &aui::impl::slot::decode_type_t<std::decay_t<decltype(v)>>
 Passes some variable and type of the variable separated by comma. It's convenient to use with the connect function (see examples). More...
 

Typedefs

template<typename... Args>
using emits = ASignal< Args... >
 A signal declaration. More...
 

Macro Definition Documentation

◆ AUI_EMIT_FOREIGN

#define AUI_EMIT_FOREIGN (   object,
  signal,
  ... 
)    (*object) ^ object->signal(__VA_ARGS__)

emits the specified signal in context of specified object.

Parameters
objectpointer-like reference to object to emit signal from.
signalsignal field name
...arguments to the signal

Unlike emit, this macro allows to emit signal of other object. It's recommended to use AUI_EMIT_FOREIGN only if there's no way to use emit.

Basic example: (in context of member function of AView):

auto view = _new<AButton>("button"); // or whatever view
AUI_EMIT_FOREIGN(view, clicked);
#define AUI_EMIT_FOREIGN(object, signal,...)
emits the specified signal in context of specified object.
Definition: AObject.h:224

This code calls slots connected to clicked signal.

If signal declaration has arguments, you have to specify them:

AUI_EMIT_FOREIGN(view, keyPressed, AInput::LCTRL);

See signal-slot system for more info.

◆ emit

#define emit   (*this) ^

emits the specified signal in context of this object.

Unlike Qt's emit, AUI's emit is not just a syntax sugar; it's required to actually perform a signal call.

Basic example: (in context of member function of AView):

emit clicked;

This code calls slots connected to clicked signal.

If signal declaration has arguments, you have to specify them in braces:

emit keyPressed(AInput::LCTRL);

See signal-slot system for more info.

Examples
/github/workspace/aui.views/src/AUI/View/AView.h.

◆ me

#define me   this, &std::remove_reference_t<decltype(*this)>

Passes the current class and type of the current class separated by comma. It's convenient to use with the connect function:

without with
connect(clicked, this, &MyObject::handleClicked);
connect(clicked, me::handleClicked);

◆ slot

#define slot (   v)    v, &aui::impl::slot::decode_type_t<std::decay_t<decltype(v)>>

Passes some variable and type of the variable separated by comma. It's convenient to use with the connect function (see examples).

Quick example:

without with
connect(clicked, myObject, &MyObject::handleClicked);
connect(clicked, slot(myObject)::handleClicked);
Note
If you are intended to reference this-> object, consider using me instead.
Examples
/github/workspace/aui.core/src/AUI/Common/AObject.h.

Typedef Documentation

◆ emits

template<typename... Args>
using emits = ASignal<Args...>

A signal declaration.

Template Parameters
Argssignal arguments

See signal-slot system for more info.

Collaboration diagram for Signal-slot: