Kết nối các tín hiệu và khe quá tải trong Qt 5


133

Tôi gặp khó khăn trong việc nắm bắt cú pháp tín hiệu / khe cắm mới (sử dụng con trỏ đến chức năng thành viên) trong Qt 5, như được mô tả trong Cú pháp khe tín hiệu mới . Tôi đã thử thay đổi điều này:

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

đến đây:

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

nhưng tôi gặp lỗi khi tôi cố biên dịch nó:

lỗi: không có chức năng phù hợp để gọi đến QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Tôi đã thử với clang và gcc trên Linux, cả hai đều có -std=c++11.

Tôi đang làm gì sai, và làm cách nào để khắc phục?


Nếu cú ​​pháp của bạn đúng, thì lời giải thích duy nhất có thể là bạn không liên kết với các thư viện Qt5, nhưng thay vào đó là Qt4. Điều này rất dễ xác minh với QtCreator trên trang 'Dự án'.
Matt Phillips

Tôi đã bao gồm một số lớp con của QObject (QSpinBox, v.v.) để có thể bao gồm QObject. Tôi đã cố gắng thêm nó bao gồm cả mặc dù và nó vẫn không được biên dịch.
dtruby

Ngoài ra, tôi chắc chắn liên kết với Qt 5, tôi đang sử dụng Qt Creator và hai bộ dụng cụ tôi đang thử nghiệm với cả hai đều có Qt 5.0.1 được liệt kê là phiên bản Qt của họ.
dtruby

Câu trả lời:


243

Vấn đề ở đây là có hai tín hiệu với tên đó: QSpinBox::valueChanged(int)QSpinBox::valueChanged(QString). Từ Qt 5.7, có các hàm trợ giúp được cung cấp để chọn quá tải mong muốn, do đó bạn có thể viết

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

Đối với Qt 5.6 trở về trước, bạn cần cho Qt biết bạn muốn chọn loại nào, bằng cách chuyển nó sang đúng loại:

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

Tôi biết, nó xấu . Nhưng không có cách nào khác. Bài học hôm nay là: đừng quá tải tín hiệu và khe cắm của bạn!


Phụ lục : điều thực sự khó chịu về dàn diễn viên là

  1. một lần lặp lại tên lớp hai lần
  2. người ta phải chỉ định giá trị trả về ngay cả khi nó thường void(đối với tín hiệu).

Vì vậy, đôi khi tôi đã thấy mình sử dụng đoạn trích C ++ 11 này:

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

Sử dụng:

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

Cá nhân tôi thấy nó không thực sự hữu ích. Tôi hy vọng vấn đề này sẽ tự biến mất khi Creator (hoặc IDE của bạn) sẽ tự động chèn đúng cast khi tự động hoàn thành thao tác lấy PMF. Nhưng trong khi đó ...

Lưu ý: cú pháp kết nối dựa trên PMF không yêu cầu C ++ 11 !


Phụ lục 2 : trong các chức năng của trình trợ giúp Qt 5.7 đã được thêm vào để giảm thiểu điều này, được mô phỏng theo cách giải quyết của tôi ở trên. Người trợ giúp chính là qOverload(bạn cũng đã có qConstOverloadqNonConstOverload).

Ví dụ sử dụng (từ các tài liệu):

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)

Phụ lục 3 : nếu bạn xem tài liệu về bất kỳ tín hiệu quá tải nào, thì bây giờ giải pháp cho vấn đề quá tải được nêu rõ trong chính các tài liệu. Chẳng hạn, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 nói

Lưu ý: Giá trị tín hiệu Thay đổi bị quá tải trong lớp này. Để kết nối với tín hiệu này bằng cách sử dụng cú pháp con trỏ hàm, Qt cung cấp một trình trợ giúp thuận tiện để lấy con trỏ hàm như trong ví dụ này:

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

1
Ah yeah, điều đó rất có ý nghĩa. Tôi đoán đối với những trường hợp như thế này khi tín hiệu / khe cắm bị quá tải, tôi sẽ chỉ sử dụng cú pháp cũ :-). Cảm ơn!
dtruby

17
Tôi đã rất phấn khích về cú pháp mới ... bây giờ là một sự thất vọng lạnh lùng của sự thất vọng đóng băng.
RushPL

12
Đối với những người thắc mắc (như tôi): "pmf" là viết tắt của "con trỏ tới hàm thành viên".
Vicky Chijwani

14
Cá nhân tôi thích sự static_castxấu xí hơn cú pháp cũ, đơn giản vì cú pháp mới cho phép kiểm tra thời gian biên dịch cho sự tồn tại của tín hiệu / khe cắm nơi cú pháp cũ sẽ thất bại khi chạy.
Vicky Chijwani

2
Thật không may, không làm quá tải tín hiệu thường không phải là một tùy chọn mà Qt Qt thường làm quá tải tín hiệu của chính họ. (ví dụ QSerialPort)
PythonNut

14

Thông báo lỗi là:

lỗi: không có chức năng phù hợp để gọi đến QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Phần quan trọng của vấn đề này là đề cập đến " loại hàm quá tải chưa được giải quyết ". Trình biên dịch không biết bạn có ý nghĩa QSpinBox::valueChanged(int)hay QSpinBox::valueChanged(QString).

Có một số cách để giải quyết tình trạng quá tải:

  • Cung cấp một tham số mẫu phù hợp để connect()

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

    Điều này buộc connect()phải giải quyết &QSpinBox::valueChangedvào tình trạng quá tải cần một int.

    Nếu bạn có quá tải chưa được giải quyết cho đối số vị trí, thì bạn sẽ cần cung cấp đối số mẫu thứ hai cho connect(). Thật không may, không có cú pháp nào để yêu cầu người đầu tiên được suy luận, vì vậy bạn sẽ cần cung cấp cả hai. Đó là khi cách tiếp cận thứ hai có thể giúp:

  • Sử dụng một biến tạm thời của loại chính xác

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

    Việc gán signalsẽ chọn quá tải mong muốn và bây giờ nó có thể được thay thế thành công vào mẫu. Điều này hoạt động tốt như nhau với đối số 'slot' và tôi thấy nó ít cồng kềnh hơn trong trường hợp đó.

  • Sử dụng chuyển đổi

    Chúng ta có thể tránh static_castở đây, vì nó chỉ đơn giản là một sự ép buộc thay vì loại bỏ các biện pháp bảo vệ ngôn ngữ. Tôi sử dụng một cái gì đó như:

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

    Điều này cho phép chúng tôi viết

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

8

Trên thực tế, bạn chỉ có thể bọc khe của mình bằng lambda và điều này:

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

sẽ nhìn tốt hơn : \


0

Các giải pháp trên hoạt động, nhưng tôi đã giải quyết vấn đề này theo một cách hơi khác, bằng cách sử dụng macro, vì vậy chỉ trong trường hợp ở đây là:

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

Thêm điều này trong mã của bạn.

Sau đó, ví dụ của bạn:

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

Trở thành:

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

2
Giải pháp "trên" là gì? Đừng cho rằng câu trả lời được trình bày cho mọi người theo thứ tự bạn đang nhìn thấy chúng!
Toby Speight

1
Làm thế nào để bạn sử dụng điều này cho quá tải mất nhiều hơn một đối số? Không phải dấu phẩy gây ra vấn đề? Tôi nghĩ rằng bạn thực sự cần phải vượt qua parens, tức là #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- được sử dụng như CONNECTCAST(QSpinBox, valueChanged, (double))trong trường hợp này.
Toby Speight

đó là một macro hữu ích tuyệt vời khi dấu ngoặc được sử dụng cho nhiều đối số, như trong nhận xét của Toby
ejectamenta
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.