목차
Signals & Slots
설명
레퍼런스: http://qt-project.org/doc/qt-5/signalsandslots.html
콜백 함수보다 더욱 발전된 스타일의 객체 통신 기법. 이벤트와는 약간 다르다. 메시지를 보내 메시지 큐에 쌓인 메시지를 하나씩 처리하는 것이 이벤트 구동 방식이라면, 이것은 시그널을 보냈을 때 즉시즉시 받아 처리하며, 그러므로 거의 callback과 유사하다고 보면 된다.
'시그널'은 의미 그대로 한 객체에서 다른 객체로 어떤 데이터를 전달할 것을 지시하는 신호이고, '슬롯'은 신호를 받은 다른 객체가 어떻게 동작할지를 정의하기 위한 수단이다. 시그널은 moc가 생성하는 C++ 확장인 반면 슬롯은 그냥 일반 함수이다. 다시 말해 어떤 한 객체에서 시그널을 보내면 그 시그널을 받아 처리하기로 한 다른 객체에서는 슬롯으로 정의된 함수를 호출한다는 것.
여기서 시그널과 슬롯을 선언한다고만 해서 무조건 두 객체간에 통신이 이뤄지는 것은 아니다. 둘을 명시적으로 엮어 주어야 시그널의 메시지가 슬롯으로 전달된다. 이 역할을 하는 것이 QObject::connect()
함수이다. 반대의 역할을 하는 함수는 QObject::disconnect()
SIGNAL(), SLOT() 매크로
connect() 함수의 슬롯 인자로는 함수 포인터도 가능하지만, 람다 함수의 형태도 가능하다. 또한 함수 포인터를 입력하는 곳에 각각 SIGNAL()
, SLOT()
매크로를 사용 가능한데 함수 포인터 형태가 아닌 함수 형태로 입력 가능하다. 즉,
connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
이렇게 함수를 호출할 수도 있지만, 아래와 같이도 가능하다.
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
이 때 SIGNAL의 인자 개수가 SLOT의 인자 개수보다 적어서는 안 된다. 런타임 에러가 날 것이다.
객체 시그널 매핑
어떤 객체의 시그널을 특정한 객체와 매핑하는 방법이 있다. QSignalMapper
를 이용해 특정 객체의 시그널은 다른 특정 객체의 슬롯으로 매핑 가능하다.
signalMapper = new QSignalMapper(this); signalMapper->setMapping(taxFileButton, QString("taxfile.txt")); signalMapper->setMapping(accountFileButton, QString("accountsfile.txt")); signalMapper->setMapping(reportFileButton, QString("reportfile.txt")); connect(taxFileButton, &QPushButton::clicked, signalMapper, &QSignalMapper::map); connect(accountFileButton, &QPushButton::clicked, signalMapper, &QSignalMapper::map); connect(reportFileButton, &QPushButton::clicked, signalMapper, &QSignalMapper::map);
taxFileButton은 texfile.txt 문자열을, accountFileButton은 accountsfile.txt 문자열을 보내도록 매핑되어 있다. 각 버튼을 클릭할 때 해당 문자열의 파일을 읽으려 한다면 다음처럼 connect를 사용하면 된다.
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(readFile(QString)));
자동 시그널 연결
http://qt-project.org/doc/qt-5/designer-using-a-ui-file.html#automatic-connections
uic에 의해 특정 이름을 가진 슬롯은 자동으로 시그널과 연결이 된다. 다음 규칙을 보자.
void on_<object name>_<signal name>(<signal parameters>);
일례로 void on_okButton_clicked()
는 okButton이란 위젯 내의 객체가 클릭되었을 때 자동으로 호출된다.
QMetaObject
는 어떤 오브젝트를 이러한 이름 규칙에 따라 시그널을 연결하도록 만드는 함수이다. 메타 오브젝트를 보면 이게 C++ 인지 파이썬 스크립트인지 헷갈리게 한다.
예제: signal_emitter, signal_receiver
HelloWorld 예제의 signal_emitter를 약간 수정한 시그널 주고 받기 예제를 기록해 본다.
우선 signal_emitter에 시그널을 추가한다.
void send(const int); </code cpp> signal_receiver 클래스를 만든다. 물론 QObject를 상속 받아야 한다. 그리고 다음 함수를 슬롯으로 등록한다. <code cpp> void receive(const int value); void signal_receiver::receive(const int value) { std::cout << "received: " << value << std::endl; }
main 함수에서 시그널과 슬롯을 연결한다.
// it is assigned at stack. You must declare after the parent. signal_receiver receiver(&app); // emitter-receiver connection QObject::connect(emitter, &signal_emitter::send, &receiver, &signal_receiver::receive);
signal_emitter의 launch() 함수에 시그널을 보내는 코드를 추가한다.
for(int i = 0; i < 10; ++i) { emit send(i); }
결과
Hello, world! received: 0 received: 1 received: 2 received: 3 received: 4 received: 5 received: 6 received: 7 received: 8 received: 9
변경된 소스는 여기서 다운로드 가능하다.
시그널은 오버로딩 가능할까?
출처: http://stackoverflow.com/a/16795664
우격다짐으로 가능한 것 같아 보이지만, 하지 말자. 좋지 않다.