이번에는 qt 가 제공하는 슬롯 대신에 직접 시그널을 처리하는 슬롯을 만들어 봐야겠지요.
  1. 링크시 에러가 나는 소스
  2. 클래스 선언부를 헤더로 분리
  3. 프로그램 모듈 구성 제안

사용자 슬롯을 만들기 위해서는 MOC(Meta Object Compiler) 에 대해 좀 알아야 합니다. qt 는 qt 컴파일러가 따로 있는 것이 아니라 한단계 거쳐서 c++ 컴파일러로 컴파일됩니다. 문제는 시그널에 대한 슬롯 처리는 qt 에서 처리할 수 없기 때문에, 작성한 프로그램을 가지고 컴파일해서는 시그널 처리를 하실 수 없습니다.

번거롭지만, 슬롯 처리를 할 수 있도록 소스를 다시 만들어 주어야 합니다. 그러나 프로그래머가 직접 만들어야 한다면 너무 성가신 일이겠죠. 이 성가신 일을 대신해 주는 것이 MOC 입니다.

MOC 를 사용하지 않고 바로 컴파일해 버리면 링크까지 이상없이 끝나지만 실행 시에 시그널, 즉 이벤트가 발생해도 슬롯 함수로의 호출이 이루어지지 않습니다.

그러므로 qt 로 만들어진 소스에다가 컴파일하기 전에 MOC 를 호출해서 소스를 다시 만들고, 그리고 난 후에 컴파일하도록 처리해 주어야 하는데, 이 때 사용하는 것이 Q_OBJECT 라는 매크로 함수입니다.

아래와 같이 MOC 사용을 위해 내용을 추가합니다.

  1. 클래스 선언부에 Q_OBJECT 를 넣어 주어야 하고,
  2. 접근 제한자에 slot 키워드를 추가해 주어야 합니다.

아래의 소스는 MOC 를 사용하기 위해서 위의 2가지를 처리한 코드입니다. 그러나 내용을 보면 아무 이상이 없지만 링크 시에 에러가 발생합니다.

링크시에 에러 발생하는 main.cpp

#include <qapplication.h>
#include <qwidget.h>
#include <qpushbutton.h>

#define kor(str) QString::fromLocal8Bit(str)

class TForm:public QWidget
{
Q_OBJECT
private:
  QPushButton *btn;
private slots: // slot 의 이벤트와 처리될 함수
  void OnClicked();
public:
  TForm();
};

TForm::TForm()
{
  resize(130, 90);
  btn = new QPushButton("&push!!", this);
  btn->setGeometry( 20, 20, 80, 40);
  connect( btn, SIGNAL( clicked()), this, SLOT(OnClicked()));

  setCaption("User Slot");
}

void TForm::OnClicked()
{
  setCaption( "btn clicked!!");
}

int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  TForm frm;

  app.setMainWidget( &frm);
  frm.resize( 300, 100);
  frm.show();

  return app.exec();
}

코드에 이상이 없지만, 링크시에 아래와 같은 이상한 에러가 발생합니다. 정말 황당하죠.

main.o(...) In function 'TForm::TForm[not-in-charge]()':
: undefined reference to 'vtable for TForm'

결론부터 말씀 드리면 클래스 선언부를 헤더파일로 이동하시고 다시 컴파일하시면 됩니다.

참고로 프로그램은 가급적 모듈화해서 작성하시는 것이 좋습니다.

클래스 선언부를 헤더파일로 이동

main.cpp

#include <qapplication.h>
#include <qwidget.h>
#include <qpushbutton.h>
#include "main.h"

#define kor(str) QString::fromLocal8Bit(str)

class TForm:public QWidget
{
Q_OBJECT
private:
  QPushButton *btn;
private slots: // slot 의 이벤트와 처리될 함수
  void OnClicked();
public:
  TForm();
};

TForm::TForm()
{
  resize(130, 90);
  btn = new QPushButton("&push!!", this);
  btn->setGeometry( 20, 20, 80, 40);
  connect( btn, SIGNAL( clicked()), this, SLOT(OnClicked()));

  setCaption("User Slot");
}

void TForm::OnClicked()
{
  setCaption( "btn clicked!!");
}

int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  TForm frm;

  app.setMainWidget( &frm);
  frm.resize( 300, 100);
  frm.show();

  return app.exec();
}


main.h

#ifndef JWSLOT_H
#define JWSLOT_H

#include <qapplication.h>
#include <qwidget.h>
#include <qpushbutton.h>

#define kor(str) QString::fromLocal8Bit(str)

class TForm:public QWidget
{
Q_OBJECT
private:
  QPushButton *btn;
private slots:
  void OnClicked();
public:
  TForm();
};

#endif

자 이제 progen 과 tmake 로 makefile 을 만들고 컴파일하겠습니다.

그런 컴파일하면 또 같은 undefined reference to 'vtable for TForm' 에러가 발생합니다.

# progen -o app.pro main.cpp
# tmake -o Makefile app.pro

왜일까요? MOC 를 거치면서 나오는 c 소스파일들을 보면 예상이 되는데요, 만들어진 main.h 도 MOC 에 정보로 주어야 합니다. 그러므로 프로젝트 파일에 main.h 헤더 내용을 추가하고 Makefile 을 다시 만들면 됩니다.

# progen -o app.pro main.cpp main.h
# tmake -o Makefile app.pro

이제 컴파일해 보면 아무 이상없이 컴파일되고 실행파일이 생성됩니다.

프로그램 모듈 구성 제안

프로그램 모듈을 만들 때, main.cpp 와 main.h 로 분리하기 보다는 main.cpp 는 "클래스 생성" 에서 언급했듯이,

  1. 위젯을 생성하고,
  2. 메인위젯을 지정한다.
  3. 프로그램을 실행한다.

와 같은 내용으로 구성하고, 클래스에 대한 선언과 구현은 따로 파일로 만드는 것이 좋습니다. 즉, 위와 같은 소스는

  • main.cpp
  • main.h

보다는

  • main.cpp
  • form.cpp
  • form.h

로 나누는 것이 좋습니다.

 

main.cpp

#include <qapplication.h>
#include <qwidget.h>
#include "form.h"

int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  TForm frm;

  app.setMainWidget( &frm);
  frm.resize( 300, 100);
  frm.show();

  return app.exec();
}


form.cpp

#include <qapplication.h>
#include <qwidget.h>
#include <qpushbutton.h>
#include "form.h"

#define kor(str) QString::fromLocal8Bit(str)

TForm::TForm()
{
  resize(130, 90);
  btn = new QPushButton("&push!!", this);
  btn->setGeometry( 20, 20, 80, 40);
  connect( btn, SIGNAL( clicked()), this, SLOT(OnClicked()));

  setCaption("User Slot");
}

void TForm::OnClicked()
{
  setCaption( "btn clicked!!");
}


form.h

#ifndef JWSLOT_H
#define JWSLOT_H

#include <qapplication.h>
#include <qwidget.h>
#include <qpushbutton.h>

class TForm:public QWidget
{
Q_OBJECT
private:
  QPushButton *btn;
private slots:
  void OnClicked();
public:
  TForm();
};

#endif

아래와 같이 Makefile 을 만들고 컴파일합니다.

# progen -o app.pro main.cpp form.cpp form.h
# tmake -o Makefile app.pro

 

태그: *QT *그래픽