2018/09/28

Signals & Slots - 가칭 SigLots 기본 사용법


가칭 SigLots의 사용법을 정리한다. Boost Signals2 Tutorial을 보면 사용법이 비슷하므로 도움이 될 둣하다. Qt의 Signals & Slot 사용법도 비슷하다. 하지만, 구현방식이 다르고 구현이 안된 부분들이 있을 수 있기 때문에 3가지 모두 사용법이 다를 수 밖에 없다. Boost 사용자 들중에는 macro를 써서 Qt 방식을 흉내 내기도 하더라.

SigLots는 현재 Signal.h header file 하나뿐이라 이것만 include해서 사용하면 된다. 지원되지 않는 컴파일러가 많으니 큰 기대는 금물이다. 우분투를 비롯한 리눅스는 문제 없겠고, Windows에서도 Mingw로 시험해 봤는데 잘 굴러간다.

기본 사용법

아래 Hello World 에서 알 수 있듯이, Signal을 선언해서 Slot을 연결하고, Signal을 방출하면 Slot(여기서는 lambda) 함수가 실행된다. 연결을 끊는 것은 수동으로도 가능하고 프로그램 종료시 자동으로 해제된다.

#include "Signal.h"
#include <iostream>

using namespace ZAPARY;

int main()
{
  Signal<void()> s; // Signal 선언
  s.connect([] { std::cout << "Hello, "; }); // Slot#1 연결
  s.connect([] { std::cout << "world~!\n"; }); // Slot#2 연결
  s.emit(); // Signal 방출 ("Hello world~!" 출력)
  s.disconnect(); // 모든 Slot 삭제 - 자동으로 호출되므로 사용안해도 됨
}

모든 호출 가능한 함수 객체를 Signal에 연결할 수 있는데, Signal은 함수형 템플릿이므로 연결하려는 함수 객체 들은 동일한 signature를 가져야만 한다. std::function과 유사하지만 Signal은 다수의 함수 객체를 저장할 수 있는 container이다.

아래의 소스는 기본적인 사용법을 보여 주기 위한 것이다. 앞서 올린 소스를 좀더 사용자 관점에서 수정했다. SigLots를 구현하면서 lambda를 다루는 게 생각보다 쉽지 않다는 것을 알았는데 그 만큼 활용도가 높다는 뜻일 거다. 아무튼 lambda도 잘 굴러간다.

아래 예제에서는 한 개의 Signal에 언제든지 여러 종류의 함수 객체 Slot이 connect/disconnect 할 수 있고, 다양한 방법으로 disconnect 할 수 있으며, 특정 Slot을 block/unblock 할 수 있음을 보여 주고 있다. 또한, Signal container 내의 Slot 들을 for loop으로 일괄 처리할 수도 있고, Slot의 실행 결과 값을 활용할 수도 있다. 주의할 점은 Slot ID는 Slot 고유 식별자이므로 container index와는 다르다. container에는 수시로 Slot들이 채워졌다 사라질 수 있기 때문이다. 그래서 인덱스는 signal[i]로 표현하고, id를 참조할 때는 signal(id)로 참조한다. Boost Tutorial 보니까 Signal 호출시 signal(...)과 같이 사용하던데, SigLots에서는 signal.emit(...)를 사용하고, ()연산자는 id 참조용이다.

내 생각에는 signal.emit(...)를 사용하는 것이 명확한 의미를 전달하는 듯이 보인다. Qt의 영향일 듯... Qt는 아예 moc의 keyword로 emit를 사용하기 때문이다. Qt의 3가지 c++ 확장 키워드가 signals/slots/emit 이다. Qt creator 사용해 보면 완전히 c++ 키워드인줄 알게 만들 정도다. 아래의 예는 쉬운 사용법이고, class 객체 간의 Signals & Slots에 적용될 때 큰 힘을 발휘하게 된다. Qt가 UI 중심 framework이다 보니 Signals & Slots가 framework의 중심 축이될 수 밖에 없는 것도 사실이다. 그래서 Qt의 예제도 모두 class에서 시작한다.

class 에서 SigLots 사용법은 다음에 이어서 정리한다.

#include "Signal.h"
#include <iostream>
#include <functional>

using namespace ZAPARY;

double sum(int n, double x)
{
  double result = n + x;
  std::cout << "function sum: " << result << '\n';
  return result;
}

double product(int n, double x)
{
  double result = n * x;
  std::cout << "function product: " << result << '\n';
  return result;
}

int main()
{
  Signal<double(int, double)> s;

  int id_product = s.connect(&product); // +Slot #1: static function, Slot ID stored.

  std::function<double(int, double)> f_sum = &sum, f_product = &product;
  f_sum(1, 2);
  s.connect(&f_sum); // +Slot #2: std::function

  int num = 1000;
  auto lambda = [](int n, double x) {
    double result = n + x;
    std::cout << "uncaptured lambda: " << result << '\n';
    return result;
  };

  lambda(1, 2);
  s.connect(&lambda); // +Slot #3: uncaptured lambda with variable
  std::cout << "+++++ Signal emit: begin +++++\n";
  s.emit(1, 2);
  std::cout << "----- Signal emit: end   -----\n\n";

  std::cout << "***** Looping each Slots *****\n"; // a Signal is container of Slots
  double sum = 0.; // use each Slot's return value
  for(int i = 0; i < s.size(); ++i) sum += s[i].emit(1, i*10); 
  std::cout << "sum = " << sum << '\n';

  s.disconnect(&f_sum); // -Slot #2, disconnect by variable name
  std::cout << "+++++ Signal emit: begin +++++\n";
  s.emit(1, 1);
  std::cout << "----- Signal emit: end   -----\n\n";

  s.disconnect(id_product); // -Slot #1, disconnect by stored Slot ID
  std::cout << "+++++ Signal emit: begin +++++\n";
  s.emit(2, 2);
  std::cout << "----- Signal emit: end   -----\n\n";

  s.connect([num] (int n, double x) // +Slot #4: captured lambda without variable
  {
    double result = n + x + num;
    std::cout << "captured lambda: " << result << '\n';
    return result;
  });
  int id_fp = s.connect(&f_product); // +Slot #5: std::function, Slot ID stored
  std::cout << "+++++ Signal emit: begin +++++\n";
  s.emit(3, 3);
  std::cout << "----- Signal emit: end   -----\n\n";

  std::cout << "Current # of Slots = " << s.size() << ",
               f_product id = " << id_fp << "\n\n";

  s(id_fp).block(); // blocking a Slot by using the stored Slot ID
  std::cout << "+++++ Signal emit: begin +++++\n";
  s.emit(4, 4);
  std::cout << "----- Signal emit: end   -----\n\n";

  std::cout << "Current # of Slots = " << s.size() << ",
               f_product id = " << s[s.size()-1].id() << "\n\n";

  s(id_fp).unblock(); // unblock the blocked Slot by using the stored Slot ID
  std::cout << "+++++ Signal emit: begin +++++\n";
  s.emit(5, 5);
  std::cout << "----- Signal emit: end   -----\n\n";
}

댓글 없음:

댓글 쓰기