Qt 신호 슬롯 목록
신호와 슬롯은 객체 간의 통신에 사용됩니다. 신호 및 슬롯 메커니즘은 Qt의 핵심 기능이며 아마도 다른 프레임워크에서 제공하는 기능과 가장 다를 것입니다. 신호와 슬롯은 Qt의 메타 객체 시스템으로 가능합니다.
GUI 프로그래밍에서 우리는 하나의 위젯을 변경할 때 일반적으로 다른 위젯에 알리기를 원합니다. 보다 일반적으로 우리는 모든 유형의 개체가 서로 통신할 수 있기를 원합니다. 예를 들어 사용자가 닫기 버튼을 클릭하면 창의 Close() 함수를 호출할 수 있습니다.
다른 툴킷은 콜백을 사용하여 이 통신을 수행합니다. 콜백 함수는 함수에 대한 포인터이므로 핸들러에서 이벤트를 알리려면 다른 함수(콜백 함수)에 대한 포인터를 핸들러에 전달해야 합니다. 그런 다음 핸들러 함수는 적절한 경우 콜백을 호출합니다. 이 접근 방식을 사용하는 성공적인 프레임워크가 존재하지만 콜백은 직관적이지 않을 수 있으며 콜백 매개변수의 유형 정확성을 보장하는 데 문제가 있을 수 있습니다.
Qt에는 콜백 기술에 대한 대안이 있습니다. 신호와 슬롯을 사용합니다. 특정 이벤트가 발생하면 신호를 보냅니다. Qt의 위젯에는 미리 정의된 많은 신호가 있지만 우리는 항상 위젯을 하위 클래스로 분류하여 자체 신호를 추가할 수 있습니다. 슬롯은 특정 신호에 대한 응답으로 호출되는 함수입니다. Qt의 위젯에는 많은 사전 정의된 슬롯이 있지만 관심 있는 신호를 처리하기 위해 위젯을 하위 클래스로 분류하고 고유한 슬롯을 추가하는 것이 일반적입니다.
신호 및 슬롯 메커니즘은 유형이 안전합니다. 신호의 서명은 수신 슬롯의 서명과 일치해야 합니다. (실제로 슬롯의 서명은 추가 인수를 무시할 수 있기 때문에 수신하는 신호보다 짧을 수 있습니다.) 서명이 호환되므로 컴파일러는 함수 포인터 기반 구문을 사용할 때 유형 불일치를 감지하는 데 도움을 줄 수 있습니다. 문자열 기반 SIGNAL 및 SLOT 구문은 런타임 시 유형 불일치를 감지합니다. 신호와 슬롯은 느슨하게 연결되어 있습니다. 신호를 내보내는 클래스는 어느 슬롯이 신호를 받는지 알지도 못하고 신경도 쓰지 않습니다. **Qt의 신호 및 슬롯 메커니즘은 신호를 슬롯에 연결하면 신호의 인수와 함께 올바른 시간에 슬롯이 호출되도록 합니다. ** 신호 및 슬롯은 모든 유형의 인수를 얼마든지 허용할 수 있습니다. 완전히 형식이 안전합니다.
QObject 또는 그 하위 클래스(예: QWidget)에서 상속하는 모든 클래스는 신호 및 슬롯을 포함할 수 있습니다. 신호는 객체가 다른 객체가 관심을 가질 만한 방식으로 상태를 변경할 때 방출됩니다. 이것이 모든 개체가 통신하기 위해 수행하는 모든 작업입니다. 자신이 보내는 신호를 수신하는 것이 있는지 여부를 모르고 상관하지 않습니다. 이것은 진정한 정보 캡슐화이며 개체가 구성 요소 기반 소프트웨어 엔지니어링에 사용될 수 있도록 합니다.
슬롯은 신호를 받는 데 사용할 수 있지만 일반 멤버 함수이기도 합니다. 물체가 자신의 신호를 수신하는지 여부를 알 수 없는 것처럼 슬롯은 신호가 연결되어 있는지 여부를 알 수 없습니다. 이렇게 하면 Qt를 사용하여 진정으로 독립적인 구성 요소를 만들 수 있습니다.
하나의 슬롯에 원하는 만큼의 신호를 연결할 수 있고, 슬롯에 원하는 만큼의 신호를 연결할 수 있습니다. 하나의 신호를 다른 신호에 직접 연결하는 것도 가능합니다. (첫 번째 신호가 방출되는 즉시 두 번째 신호가 방출됩니다.)
신호와 슬롯은 강력한 컴포넌트 프로그래밍 메커니즘을 형성합니다.
신호
개체의 클라이언트나 소유자가 관심을 가질 수 있는 방식으로 개체의 내부 상태가 변경되면 신호가 방출됩니다. 신호는 공개 액세스 기능이며 어디에서나 내보낼 수 있지만 신호를 정의하는 클래스와 해당 하위 클래스에서만 신호를 내보낼 것을 권장합니다.
신호가 방출되면 신호에 연결된 슬롯은 일반적으로 일반 함수 호출과 마찬가지로 즉시 실행됩니다. 이 경우 신호 및 슬롯 메커니즘은 GUI 이벤트 루프와 완전히 독립적입니다. 모든 슬롯이 반환되면 emit 문 다음의 코드가 실행됩니다. 대기 중인 연결을 사용할 때는 상황이 약간 다릅니다. 이 경우 emit 키워드 다음의 코드는 즉시 계속되고 슬롯은 나중에 실행됩니다.
여러 슬롯이 신호에 연결된 경우 해당 슬롯은 신호가 방출될 때 연결된 순서대로 차례로 실행됩니다.
신호는 moc에 의해 자동으로 생성되며 에 있을 수 없습니다. CPP 파일. 반환 유형을 가질 수 없습니다(즉, void 사용).
매개변수에 대한 참고 사항: 우리의 경험에 따르면 신호와 슬롯은 특수 유형을 사용하지 않는 경우 더 재사용할 수 있습니다. QScrollBar::valueChanged()가 가상의 QScrollBar::Range와 같은 특수 유형을 사용하는 경우 QScrollBar용으로 특별히 설계된 슬롯에만 연결할 수 있습니다. 서로 다른 입력 위젯을 함께 연결할 수 없습니다.
슬롯
슬롯에 연결된 시그널이 방출되면 슬롯이 호출됩니다. 슬롯은 일반적인 C++ 함수이며 일반적으로 호출할 수 있으며 유일한 속성은 신호를 슬롯에 연결할 수 있다는 것입니다.
슬롯은 일반 멤버 함수이기 때문에 직접 호출될 때 일반 C++ 규칙을 따릅니다. 그러나 슬롯으로서 신호-슬롯 연결을 통해 액세스 수준에 관계없이 모든 구성 요소에서 호출할 수 있습니다. 이는 임의 클래스의 인스턴스에서 방출된 신호로 인해 관련 없는 클래스의 인스턴스에서 개인 슬롯이 호출될 수 있음을 의미합니다.
또한 슬롯을 가상으로 정의할 수 있으며 실제로 매우 유용한 것으로 나타났습니다.
시그널과 슬롯은 더 많은 유연성을 제공하기 때문에 콜백보다 약간 느리지만 실제 애플리케이션의 경우에는 이 점에서 차이가 그리 크지 않습니다. 일반적으로 어떤 슬롯에 연결된 신호를 방출하는 것은 가상이 아닌 함수 호출을 수신기에 직접 호출하는 것보다 약 10배 느립니다. 이것은 연결 개체를 찾고, 모든 연결을 안전하게 반복하고(즉, 전송 중에 후속 수신자가 파괴되는지 확인) 일반적인 방식으로 모든 매개 변수를 마샬링하는 데 필요한 오버헤드입니다. 10개의 비가상 함수 호출이 많은 것처럼 들릴 수 있지만 새 작업 또는 삭제 작업보다 오버헤드가 훨씬 적습니다. 문자열, 벡터 또는 목록 작업을 수행한 후에는 배경을 새로 만들거나 삭제해야 하며 신호 및 슬롯 오버헤드는 전체 함수 호출 비용의 작은 부분에 불과합니다. 슬롯에서 syscall을 수행하든 간접적으로 10개 이상의 함수를 호출하든 마찬가지입니다. 신호 및 슬롯 메커니즘의 단순성과 유연성은 오버헤드를 충분히 감수할 가치가 있으며 사용자는 이를 알아채지도 못할 것입니다.
신호 또는 슬롯이라는 변수를 정의하는 다른 라이브러리는 Qt 기반 응용 프로그램으로 컴파일할 때 컴파일러 경고 및 오류를 유발할 수 있습니다. 이 문제를 해결하려면 # undef 전처리기 기호를 사용하십시오.
Qt에서 연결 사용
이전 버전의 쓰기 방법은 더 복잡하며 형식 매개변수를 포함하여 신호 및 슬롯을 명확하게 지정해야 합니다.
class MyButton : public QWidget
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
signals:
void sigClicked();
void sigClicked(bool check);
};
구버전이 한눈에 쏙쏙 들어오는데 더 번거롭다.
connect(m_pBtn,SIGNAL(sigClicked()),this,SLOT(onClicked()));
connect(m_pBtn,SIGNAL(sigClicked(bool)),this,SLOT(onClicked(bool)));
Qt5.0 이후에 작성하는 방법
connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked);
신호가 과부하되면 오류가 보고됩니다.
오류: 'connect' connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked) 호출에 일치하는 멤버 함수가 없습니다.
시그널 함수의 특정 버전을 지정하려면 다음 작성 방법을 사용하십시오. 슬롯 함수가 과부하인 경우 이전 방법을 사용할 수도 있습니다.
connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, &Widget::onClicked);
신호 함수 과부하 최적화
그러나 여전히 오버로드된 슬롯 기능을 연결할 수 없습니다.
connect(m_pBtn, QOverload<bool>::of(&MyButton::sigClicked),this,&Widget::onClicked);
Lambda 함수 작성 방법
슬롯 함수의 내용이 단순하다면 슬롯 링크를 따로 정의할 필요 없이 람다 함수를 직접 사용하는 것이 편리하다.
connect(m_pBtn, QOverload<bool>::of(&MyButton::sigClicked),
[=](bool check){
/* do something.. */
});
connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, [=](bool check){
//do something
});
람다 함수
[capture](parameters) mutable ->return-type{statement}
1.[캡처]: 목록을 캡처합니다. 캡처 목록은 항상 Lambda 함수의 시작 부분에 나타납니다. 실제로 []는 람다 인용문입니다. 컴파일러는 elicitor를 기반으로 다음 코드가 Lambda 함수인지 판단합니다. 캡처 목록은 Lambda 함수에서 사용할 컨텍스트의 변수를 캡처할 수 있습니다.
2. (매개변수): 매개변수 목록. 일반 함수의 매개변수 목록과 일치합니다. 매개변수 전달이 필요하지 않은 경우 괄호 "()"와 함께 생략할 수 있습니다.
3. 변경 가능: 변경 가능 수정자. 기본적으로 Lambda 함수는 항상 const 함수이며 mutable은 해당 constness를 취소할 수 있습니다. 이 수정자를 사용할 때 매개변수 목록을 생략할 수 없습니다(매개변수가 비어 있더라도).
4.->반환 유형: 반환 유형. 추적 반환 형식 형식으로 함수의 반환 형식을 선언합니다. 반환 값이 필요하지 않은 경우 "->" 기호와 함께 생략할 수도 있습니다. 또한 반환 유형이 명확한 경우 컴파일러가 반환 유형을 추론할 수 있도록 이 부분도 생략할 수 있습니다.
5. {문장}: 함수 본문. 내용은 일반 함수와 동일하지만 파라미터 외에 캡처된 모든 변수도 사용할 수 있습니다.
일반 함수와 가장 큰 차이점은 매개 변수 외에도 Lambda 함수는 캡처 목록을 통해 일부 컨텍스트에서 데이터에 액세스할 수 있다는 것입니다. 특히 캡처 목록은 Lambda에서 사용할 수 있는 컨텍스트의 데이터와 이를 사용하는 방법(값 기준 또는 참조 기준)을 설명합니다. 구문적으로 "[]"에 포함되는 것은 캡처 목록이며 캡처 목록은 쉼표로 구분된 여러 캡처 항목으로 구성됩니다. 캡처 목록은 다음 형식으로 제공됩니다.
1. [var]는 값 전송 방법이 변수 var를 캡처함을 나타냅니다.
2. [=]는 값 전송 방법이 상위 범위(이를 포함하여)의 모든 변수를 캡처함을 나타냅니다.
3. [&var]는 참조 전송 방법이 변수 var를 캡처함을 나타냅니다. 변수 var;
4. [& ]는 참조 전송 방법이 상위 범위(this 포함)의 모든 변수를 캡처함을 의미합니다.
5. [this]는 값 전송 방법이 현재 this 포인터를 캡처함을 의미합니다.
위에서 언급한 상위 범위, 즉 Lambda 함수를 포함하는 문 블록, 쉽게 말해서 Lambda를 포함하는 "{}" 코드 블록입니다. 위의 캡처 목록은 다음과 같이 결합할 수도 있습니다.
1.[=,&a,&b]는 변수 a와 b를 참조로 캡처하고 다른 모든 변수는 값으로 캡처하는 것을 의미합니다. -reference 메서드는 다른 모든 변수를 캡처합니다.
그러나 캡처 목록은 변수가 반복적으로 전달되는 것을 허용하지 않는다는 점은 주목할 가치가 있습니다. 다음 예제는 컴파일 시간 오류로 이어지는 일반적인 중복입니다. 예를 들어:
3. [=, a] 여기서는 모든 변수를 값 전달로 캡쳐했지만 a가 반복적으로 캡쳐되면 오류가 발생함
4. [&,&this] 여기 &에서 모든 변수를 참조 전달로 캡쳐한 후 이것을 캡쳐 역시 반복입니다.
람다 함수 예시
[캡처] 설명
이전 섹션의 프로젝트를 기반으로 mybutton_1 버튼을 추가하고 버튼을 누른 다음 버튼 텍스트를 변경합니다. 위 람다 식의 기본 구성 코드에 따르면 다음과 같이 쓸 수 있습니다(잘못된 표기법).
QPushButton *mybutton_1=new QPushButton(this);
mybutton_1->setText("关注<程序媛讲QT>");
mybutton_1->move(150,100); //移动按键
connect(mybutton_1,&QPushButton::pressed,
[]()
{
mybutton_1->setText("Lambda表达式");
}
);
이때 컴파일 시 "error: 'mybutton_1' is not capture"라는 오류 메시지가 표시되는데, 이는 mybutton_1이 내 범위에 없다는 의미입니다. 이때 [capture]를 사용하여 외부 변수를 전달해야 합니다. 이 시점에서 mybutton_1만 전달하면 코드가 정상적으로 컴파일되고 실행될 수 있습니다. connect() 함수 코드는 다음과 같이 변경됩니다.
connect(mybutton_1,&QPushButton::pressed,
[mybutton_1]()
{
mybutton_1->setText("Lambda表达式");
}
);
이 시점에서 코드를 컴파일하고 정상적으로 실행할 수 있습니다. 그러면 외부에 변수가 여러 개 있을 때 일일이 []에 쓰기가 번거로우니 이때 "="를 사용하면 됩니다. 예를 들어:
QPushButton *mybutton_1=new QPushButton(this);
mybutton_1->setText("关注<程序媛讲QT>");
mybutton_1->move(150,100); //移动按键
int a=2,b=7;
connect(mybutton_1,&QPushButton::pressed,
[=]()
{
mybutton_1->setText("Lambda表达式");
qDebug()<<a+b;
}
);
qDebug()는 C 언어의 printf와 비슷한 출력물입니다. 사용할 때 #include "QDebug"가 필요합니다.
컴파일하고 실행하면 버튼을 누른 후 버튼 텍스트가 변경되고 텍스트 "9"가 출력됩니다. 외부 변수 a, b, mybutton_1의 값에 "="가 전달되었음을 알 수 있다.
"="는 모든 외부 지역 변수와 클래스의 모든 구성원을 값으로 전달할 수 있음을 알 수 있습니다. 마찬가지로 "this"는 클래스의 모든 멤버가 값으로 전달됨을 의미하고 "&"는 모든 외부 지역 변수가 참조됨을 의미합니다.
요약하다:
- [capture]: 람다 식에서 이 부분은 반드시 존재해야 하며 생략할 수 없습니다.
- =: 모든 외부 지역 변수와 클래스의 모든 구성원을 값으로 람다 식에 전달합니다.기본값은 읽기 전용이며 변수 값은 변경할 수 없습니다.
- &: 모든 지역 변수(람다식이 위치한 클래스에서 이것을 포함), 참조의 전달 방식.
- this: Lambda가 위치한 클래스의 멤버 변수를 함수 본문에 사용할 수 있습니다.
- "="를 권장합니다.
변경 가능 설명
위에서 언급했듯이 "="는 값으로 전달됩니다. 기본적으로 읽기 전용입니다. 외부 변수를 변경하려면 이때 변경 가능을 사용할 수 있습니다. 코드는 다음과 같습니다.
connect(mybutton_1,&QPushButton::pressed,
[=]() mutable
{
b=3;
mybutton_1->setText("Lambda表达式");
qDebug()<<a+b;
}
);
- 요약하다:
- 람다 식에서 mutable은 생략할 수 있습니다. 변수를 값으로 전달할 때 가변 수정자를 추가하면 Lambda 표현식에서 변수 값을 수정할 수 있지만 값 자체는 수정되지 않습니다.
(매개변수) 설명
Lambda 표현식에 매개변수가 없으면 이 부분을 무시할 수 있으며, 신호에 매개변수가 있으면 ()를 사용하여 함수 매개변수를 오버로드할 수 있습니다. 예를 들어:
connect(mybutton_1,&QPushButton::clicked,
[=](bool Check)
{
qDebug()<<Check;
}
);
QPushButton의 신호 void QAbstractButton::clicked(bool checked = false)에는 매개변수가 있으며 매개변수는 ()를 통해 Lambda 표현식으로 전달될 수 있습니다.
요약하다:
utton::clicked,
[=](bool 확인)
{ qDebug()<<확인; } );
QPushButton的信号void QAbstractButton::clicked(bool checked = false)是有参数的,通过()可将参数传入Lambda表达式中。
**总结:**
- **在Lambda表达式中无参数时,(parameters)部分是可以省略的。**