การเชื่อมต่อสัญญาณและสล็อตที่โอเวอร์โหลดใน Qt 5


133

ฉันมีปัญหาในการจับกับไวยากรณ์สัญญาณ / สล็อตใหม่ (โดยใช้ตัวชี้ไปยังฟังก์ชันสมาชิก) ใน Qt 5 ตามที่อธิบายไว้ในใหม่สัญญาณสล็อตไวยากรณ์ ฉันลองเปลี่ยนสิ่งนี้:

QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                 slider, SLOT(setValue(int));

สำหรับสิ่งนี้:

QObject::connect(spinBox, &QSpinBox::valueChanged,
                 slider, &QSlider::setValue);

แต่ฉันได้รับข้อผิดพลาดเมื่อพยายามรวบรวม:

ข้อผิดพลาด: ไม่มีฟังก์ชันที่ตรงกันสำหรับการโทร QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

ฉันได้ลองใช้ clang และ gcc บน Linux ทั้งกับ-std=c++11ไฟล์.

ฉันทำอะไรผิดและฉันจะแก้ไขได้อย่างไร


หากไวยากรณ์ของคุณถูกต้องคำอธิบายเดียวอาจเป็นได้ว่าคุณไม่ได้เชื่อมโยงกับไลบรารี Qt5 แต่เช่น Qt4 แทน สิ่งนี้ง่ายต่อการตรวจสอบกับ QtCreator ในหน้า "โครงการ"
Matt Phillips

ฉันรวมคลาสย่อยของ QObject (QSpinBox เป็นต้น) ดังนั้นจึงควรรวม QObject ฉันได้ลองเพิ่มสิ่งนั้นแล้ว แต่ก็ยังไม่สามารถรวบรวมได้
dtruby

นอกจากนี้ฉันกำลังเชื่อมโยงกับ Qt 5 อย่างแน่นอนฉันใช้ Qt Creator และสองชุดที่ฉันทดสอบโดยทั้งสองมี Qt 5.0.1 อยู่ในรายการเป็นเวอร์ชัน Qt
dtruby

คำตอบ:


244

ปัญหาคือมีสองสัญญาณที่มีชื่อนั้น: QSpinBox::valueChanged(int)และQSpinBox::valueChanged(QString). จาก Qt 5.7 มีฟังก์ชั่นตัวช่วยสำหรับเลือกโอเวอร์โหลดที่ต้องการเพื่อให้คุณสามารถเขียนได้

connect(spinbox, qOverload<int>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

สำหรับ Qt 5.6 และก่อนหน้าคุณต้องบอก Qt ว่าต้องการเลือกอันไหนโดยการแคสต์ให้ถูกประเภท:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

ฉันรู้ว่ามันน่าเกลียด แต่ไม่มีทางแก้ไขได้ บทเรียนวันนี้คืออย่าให้สัญญาณและช่องของคุณมากเกินไป!


ภาคผนวก : สิ่งที่น่ารำคาญจริงๆเกี่ยวกับนักแสดงคือสิ่งนั้น

  1. หนึ่งซ้ำชื่อคลาสสองครั้ง
  2. ต้องระบุค่าส่งคืนแม้ว่าโดยปกติจะเป็นvoid(สำหรับสัญญาณ)

บางครั้งฉันก็พบว่าตัวเองใช้ตัวอย่าง C ++ 11 นี้:

template<typename... Args> struct SELECT { 
    template<typename C, typename R> 
    static constexpr auto OVERLOAD_OF( R (C::*pmf)(Args...) ) -> decltype(pmf) { 
        return pmf;
    } 
};

การใช้งาน:

connect(spinbox, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), ...)

ฉันเองพบว่ามันไม่มีประโยชน์จริงๆ ฉันคาดว่าปัญหานี้จะหายไปเองเมื่อผู้สร้าง (หรือ IDE ของคุณ) จะแทรกการแคสต์ที่ถูกต้องโดยอัตโนมัติเมื่อทำการเติม PMF โดยอัตโนมัติ แต่ในขณะเดียวกัน ...

หมายเหตุ: ไวยากรณ์การเชื่อมต่อที่ใช้ PMF ไม่ต้องการ C ++ 11 !


ภาคผนวก 2 : ในฟังก์ชันตัวช่วย Qt 5.7 ถูกเพิ่มเพื่อลดปัญหานี้โดยจำลองตามวิธีแก้ปัญหาของฉันข้างต้น ตัวช่วยหลักคือqOverload(คุณมีqConstOverloadและqNonConstOverload)

ตัวอย่างการใช้งาน (จากเอกสาร):

struct Foo {
    void overloadedFunction();
    void overloadedFunction(int, QString);
};

// requires C++14
qOverload<>(&Foo:overloadedFunction)
qOverload<int, QString>(&Foo:overloadedFunction)

// same, with C++11
QOverload<>::of(&Foo:overloadedFunction)
QOverload<int, QString>::of(&Foo:overloadedFunction)

ภาคผนวก 3 : หากคุณดูเอกสารของสัญญาณที่โอเวอร์โหลดตอนนี้วิธีแก้ปัญหาการโอเวอร์โหลดจะระบุไว้อย่างชัดเจนในเอกสาร ตัวอย่างเช่นhttps://doc.qt.io/qt-5/qspinbox.html#valueChanged-1พูดว่า

หมายเหตุ: ค่าสัญญาณเปลี่ยนเกินในคลาสนี้ ในการเชื่อมต่อกับสัญญาณนี้โดยใช้ไวยากรณ์ตัวชี้ฟังก์ชัน Qt มีตัวช่วยที่สะดวกในการรับตัวชี้ฟังก์ชันดังที่แสดงในตัวอย่างนี้:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });

1
ใช่แล้วมันสมเหตุสมผลมาก ฉันเดาว่าสำหรับกรณีเช่นนี้ที่สัญญาณ / สล็อตมากเกินไปฉันจะยึดติดกับไวยากรณ์เก่า :-) ขอบคุณ!
dtruby

17
ฉันตื่นเต้นมากกับไวยากรณ์ใหม่ ... ตอนนี้ความผิดหวังที่หนาวเหน็บ
RushPL

12
สำหรับผู้ที่สงสัย (เช่นฉัน): "pmf" ย่อมาจาก "pointer to member function"
Vicky Chijwani

14
โดยส่วนตัวแล้วฉันชอบstatic_castความอัปลักษณ์มากกว่าไวยากรณ์แบบเก่าเพียงเพราะไวยากรณ์ใหม่ช่วยให้สามารถตรวจสอบเวลาคอมไพล์ได้ว่ามีสัญญาณ / สล็อตที่ไวยากรณ์เก่าจะล้มเหลวในขณะรันไทม์
Vicky Chijwani

2
น่าเสียดายที่การไม่โอเวอร์โหลดสัญญาณมักไม่ใช่ทางเลือก - Qt มักจะส่งสัญญาณเกินพิกัด (เช่นQSerialPort)
PythonNut

14

ข้อความแสดงข้อผิดพลาดคือ:

ข้อผิดพลาด: ไม่มีฟังก์ชันที่ตรงกันสำหรับการโทร QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

ส่วนที่สำคัญคือการกล่าวถึง " ประเภทฟังก์ชันโอเวอร์โหลดที่ไม่ได้รับการแก้ไข " คอมไพเลอร์ไม่ทราบว่าคุณหมายถึงQSpinBox::valueChanged(int)หรือQSpinBox::valueChanged(QString).

มีหลายวิธีในการแก้ไขปัญหาการโอเวอร์โหลด:

  • ระบุพารามิเตอร์เทมเพลตที่เหมาะสมให้ connect()

    QObject::connect<void(QSpinBox::*)(int)>(spinBox, &QSpinBox::valueChanged,
                                             slider,  &QSlider::setValue);

    สิ่งนี้บังคับconnect()ให้แก้ไขปัญหา&QSpinBox::valueChangedการโอเวอร์โหลดที่ใช้เวลาint.

    ถ้าคุณมี overloads connect()ยังไม่ได้แก้ไขข้อโต้แย้งสล็อตแล้วคุณจะต้องจัดหาอาร์กิวเมนต์แม่แบบที่สองที่จะ น่าเสียดายที่ไม่มีไวยากรณ์ที่จะขอให้อนุมานก่อนดังนั้นคุณจะต้องจัดหาทั้งสองอย่าง นั่นคือเมื่อแนวทางที่สองสามารถช่วยได้:

  • ใช้ตัวแปรชั่วคราวของประเภทที่ถูกต้อง

    void(QSpinBox::*signal)(int) = &QSpinBox::valueChanged;
    QObject::connect(spinBox, signal,
                     slider,  &QSlider::setValue);

    การกำหนดให้signalจะเลือกโอเวอร์โหลดที่ต้องการและตอนนี้สามารถแทนที่ลงในเทมเพลตได้สำเร็จ สิ่งนี้ใช้ได้ดีกับอาร์กิวเมนต์ 'slot' และฉันพบว่ามันยุ่งยากน้อยกว่าในกรณีนั้น

  • ใช้การแปลง

    เราสามารถหลีกเลี่ยงstatic_castที่นี่ได้เนื่องจากเป็นเพียงการบีบบังคับแทนที่จะกำจัดการปกป้องของภาษา ฉันใช้สิ่งที่ชอบ:

    // Also useful for making the second and
    // third arguments of ?: operator agree.
    template<typename T, typename U> T&& coerce(U&& u) { return u; }

    สิ่งนี้ทำให้เราสามารถเขียนได้

    QObject::connect(spinBox, coerce<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                     slider, &QSlider::setValue);

8

จริงๆแล้วคุณสามารถพันช่องของคุณด้วยแลมด้าและสิ่งนี้:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
    slider, &QSlider::setValue);

จะดูดีขึ้น : \


0

วิธีแก้ปัญหาข้างต้นใช้งานได้ แต่ฉันแก้ไขสิ่งนี้ด้วยวิธีที่แตกต่างกันเล็กน้อยโดยใช้มาโครดังนั้นในกรณีที่นี่คือ:

#define CONNECTCAST(OBJECT,TYPE,FUNC) static_cast<void(OBJECT::*)(TYPE)>(&OBJECT::FUNC)

เพิ่มสิ่งนี้ในรหัสของคุณ

จากนั้นตัวอย่างของคุณ:

QObject::connect(spinBox, &QSpinBox::valueChanged,
             slider, &QSlider::setValue);

กลายเป็น:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);

2
แนวทางแก้ไข "ข้างบน" คืออะไร? อย่าคิดว่าคำตอบจะถูกนำเสนอให้ทุกคนตามลำดับที่คุณกำลังเห็น
Toby Speight

1
คุณใช้สิ่งนี้สำหรับโอเวอร์โหลดที่ใช้อาร์กิวเมนต์มากกว่าหนึ่งรายการได้อย่างไร ลูกน้ำไม่ก่อให้เกิดปัญหาใช่หรือไม่? ฉันคิดว่าคุณต้องผ่าน parens จริงๆนั่นคือ#define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- ใช้CONNECTCAST(QSpinBox, valueChanged, (double))ในกรณีนี้
Toby Speight

เป็นมาโครที่มีประโยชน์มากเมื่อใช้วงเล็บสำหรับอาร์กิวเมนต์หลายรายการดังในความคิดเห็นของ Toby
ejectamenta
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.