הפקודה bind הגיעה ל Qt (או: ביי ביי QSignalMapper)

12/01/2017
C++

בשעה טובה אפשר להפרד מ QSignalMapper בזכות יכולת חדשה של C++11. אם יצא לכם להשתמש בו או אם אתם מתכנתי Qt שרוצים להוסיף טריק חדש לארגז הכלים המשיכו לקרוא.

1. הבעיה: איך מזהים מאיזה כפתור הגיעה הלחיצה

את הבעיה ש QSignalMapper נכתב כדי לפתור קל לראות מקריאה מהירה של תיעוד המחלקה, כבר מהמשפט הראשון שם:

The QSignalMapper class bundles signals from identifiable senders.

בעברית, כשאתם משתמשים באותה פונקציה כדי לטפל במספר סיגנלים, הפונקציה צריכה לדעת מאיפה הגיע הסיגנל. דרך אחת היא להשתמש ב sender ולהסתכל על שם האוביקט השולח, אבל זה מסרבל את הקוד שצריך לטפל באירוע.

2. חיבור פונקציה קשורה באמצעות bind

נניח שיש לנו שלושה כפתורים שנוצרים בלולאה ואנו רוצים לחבר אותם כך שלחיצה על כל כפתור תגיע לפונקציה handleClick ותעביר לפונקציה את האינדקס של הכפתור, כלומר 0 לכפתור הראשון, 1 לשני ו-2 לשלישי. פתרון פשוט עם bind נראה כך ועובד ב Qt:

#include <functional>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QHBoxLayout *layout = new QHBoxLayout(this);

    for (int i=0; i < 3; i++) {
        QPushButton *btn = new QPushButton();
        layout->addWidget(btn);

        QObject::connect(btn, &QPushButton::clicked,
        std::bind(&MainWindow::handleClick, this, i));
    }
}

3. העמסת פונקציות

במקרה של העמסת פונקציות טיפול (overload) לפעמים תצטרכו לציין ל bind באיזה גירסא של פונקציית הטיפול תרצו להשתמש באמצעות המרה לחתימה הנכונה. כך הקוד הבא מתחיל טיימר בעקבות לחיצה על כפתור:

QObject::connect(ui->btnStart, &QPushButton::clicked,
                std::bind(static_cast<void (QBasicTimer::*)(int, QObject *)>(&QBasicTimer::start), &m_timer, 10, this));

שתי החתימות של הפונקציה QBasicTimer::start נראות כך:

void start(int msec, QObject *object)
void start(int msec, Qt::TimerType timerType, QObject *obj)

ולכן עלינו לבצע Casting כדי לספר לקומפיילר שאנו רוצים לקשור את החתימה הראשונה.

נ.ב.1 אלטרנטיבה נחמדה ל bind היא Lambda Functions, גם היא תוספת של C++11. אם אתם לא מכירים מומלץ לקרוא עליהן בתיעוד.

נ.ב.2 ניתן לבקש מ bind שיעביר גם את הפמרטרים המקוריים של הסיגנל אל פונקציית הטיפול באמצעות שמוש ב placeholders. פשוט מוסיפים בתור הפרמטר האחרון ל bind את המזהה std::placeholders::_1 עבור גישה לפרמטר הראשון, std::placeholders::_2 בשביל הפרמטר השני וכך הלאה.

נ.ב.3 הטכניקה שתוארה בפוסט נקראת curry על שם המתמטיקאי האסקל קארי. כל קשר למאכל הודי מוגבל לתמונת הכותרת בלבד.