목차

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

우격다짐으로 가능한 것 같아 보이지만, 하지 말자. 좋지 않다.