본문 바로가기

Qt

Qt QObject와 moc의 동작 원리 이해하기 ㅡ Signal/Slot과 런타임 리플렉션

들어가며

지난 글에서는 Qt의 근간이 되는 Event Loop의 동작 원리를 정리했다.

2026.01.23 - [Qt] - Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지

 

Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지

들어가며Qt는 크로스 플랫폼 애플리케이션 개발 프레임워크로,동일한 코드로 Windows, Linux, macOS, Embedded Linux 등 다양한 운영체제에서 동작하는 App을 만들 수 있다. QObject, Signal/Event, QTimer 등을 사용

prejudice.tistory.com

이번 글에서는 조금 더 클래스 관점에서 접근해,

Qt Framework의 핵심이 되는 QObject

이를 가능하게 하는 moc(Meta-Object Compiler) 개념을 정리해 보려 한다.


QObject란 무엇인가

Qt에서 사용하는 대부분의 객체는 공통적으로 QObject라는 객체 모델을 상속받는다.

 

QObject의 핵심 역할은 다음과 같다.

  • Sinal / Slot 기반 객체 간 통신 제공
  • Event Loop와 연계된 이벤트 처리
  • 객체 트리 기반 메모리 관리

즉, Signal / Slot을 사용하는 클래스라면 반드시 QObject를 상속해야 한다.


Q_OBJECT 매크로의 역할

Sinal / Slot은 단순한 C++ 함수 호출이 아니라,

Qt Event Loop 런타임에서 동적으로 처리되는 메커니즘이다.

 

이때 QObject가 런타임에서 동작할 수 있도록 도와주는 것이

바로 Q_OBJECT 매크로이다.

#include <QObject>

class MyObject : public QObject
{
    Q_OBJECT    // Signal / Slot을 사용하기 위해 반드시 필요
public:
    explicit MyObject(QObject* parent = nullptr) : QObject(parent) {}

signals:
    void valueChanged(int value);   // 시그널 선언

public slots:
    void onValueChanged(int value)  // 슬롯 선언
    {
        qDebug() << "Value changed to:" << value;
    }
};

Q_OBJECT가 없으면:

  • Sinal / Slot 사용 불가
  • invokeMethod 불가
  • Property 시스템 사용 불가

즉, QObject는 Q_OBJECT와 함께 사용할 때 비로소 완성된다.


QObject와 일반 C++ 클래스의 차이

겉으로 보면 QObject를 상속한 클래스도 일반적인 C++ 클래스와 크게 달라 보이지 않는다.

하지만 내부적으로는 Qt의 메타 객체 시스템(Meta-Object System)에 의해 관리된다는 점에서 본질적인 차이가 있다.

 

일반 C++ 클래스의 특징

  • 컴파일 타임에 타입 정보가 고정됨
  • 런타임에 클래스 구조를 동적으로 조회하기 어려움
  • 객체 간 통신은 직접 함수 호출로 구현

QObject 기반 클래스의 추가 기능

  • Signal / Slot 기반 비동기 통신
  • 확장된 런타임 타입 정보
  • 문자열 기반 메서드 호출 (invokeMethod)
  • 동적 속성(Property) 시스템
  • Parent–Child 객체 트리 기반 메모리 관리

즉, QObject는 단순한 베이스 클래스가 아니라 이벤트 기반 객체 모델의 핵심이라고 볼 수 있다.


moc (Meta-Object Compiler)

C++ 컴파일러만으로는

Signal / Slot, Property 같은 동적 기능을 구현할 수 없다.

 

이를 보완하기 위해 Qt는 moc라는 별도의 전처리 도구를 사용한다.

 

빌드 과정은 다음과 같다.

  1. Qt 빌드 시스템(qmake / CMake)이 Q_OBJECT 매크로를 발견
  2. 해당 헤더를 moc에 전달
  3. moc가 메타 정보를 담은 C++ 소스 파일 생성
  4. 생성된 moc 파일이 함께 컴파일됨

예를 들어 다음 클래스가 있다면, 

class MyObject : public QObject
{
    Q_OBJECT
signals:
    void valueChanged(int value);
};

moc는 내부적으로 다음과 같은 코드를 생성한다.

// moc_MyObject.cpp (자동 생성)
const QMetaObject MyObject::staticMetaObject = {
    // 시그널, 슬롯, 메서드 정보 테이블
};

이 메타 객체 정보 덕분에 Qt는 런타임에서 객체 구조를 탐색하고 호출할 수 있다.

좀 더 정확히 알아보면 Qt의 객체 모델은 C++을 기반으로 생성된다.
C++는 기본적으로 런타임 리플렉션 기능을 제공하지 않기 때문에
Qt는 moc를 통해 가상의 메타 객체 테이블과 간접 호출 코드C++소스로 생성해 컴파일한다.

런타임 리플렉션(Runtime Reflection)

리플렉션이란 프로그램이 실행 중에 자기 자신의 구조를 조사하고, 호출하고, 조작할 수 있는 능력을 의미한다.

예를 들어

  • 클래스 이름 조회
  • 함수(메서드) 목록 확인
  • 문자열 기반 메서드 호출

같은 기능이 가능하다면, 그 언어 또는 프레임워크는 런타임 리플렉션을 지원한다고 말한다.

 

C++의 기본 철학은 컴파일 타임 타입 안정성과 성능을 중시하는 것으로, 기본적으로 런타임 리플렉션을 지원하지 않는다.

예를 들어 C++에서는 아래와 같은 함수 호출을 지원하지 않는다.

std::string funcName = "valueChanged";
obj.call(funcName); // ❌ 불가능

 

"컴파일 시점에 만들어졌는데 왜 런타임 리플렉션?"
리플렉션은 '언제 만들어졌냐'가 아니라 '어떻게 접근하느냐'의 문제다.
moc 코드는 컴파일 시 생성되지만 그 정보는 런타임에 조회, 해석, 호출된다.

Qt에서의 런타임 리플렉션

Qt는 언어 차원의 리플렉션 대신,

moc를 통해 생성된 메타 객체 테이블을 이용해 런타임 리플렉션을 구현했다.

 

이 덕분에 다음과 같은 기능들이 가능해진다.

  • QMetaObject::invokeMethod(obj, "methodName")
  • UI 파일 기반 자동 연결 ( connectByName )
  • QML ↔ C++ 연동

또한 이 구조를 이해하면,

  • 컴파일 타임에 연결하는 connect
  • 런타임에 탐색하는 invokeMethod

사이의 성능 차이가 발생하는 이유도 자연스럽게 이해할 수 있다.


회고

QObject를 가볍게 정리하려던 글이었지만,

그 과정에서 Qt가 추구하는 객체 모델과 설계 철학을 깊이 이해할 수 있었다.

  • 왜 Qt는 메타 객체 시스템을 도입했는지
  • 왜 Signal / Slot이 함수 호출이 아닌 구조로 설계되었는지
  • C++과 Python / Java의 런타임 모델 차이

Event Loop와 함께 Qt 내부 구조를 이해하는데 큰 도움이 되는 주제였다.

다음에는 QObject구조가 QML까지 어떻게 확장되는지, C++에서 공식적으로 제공하는 런타임 리플렉션에 대해 공부하고 싶다.

 

부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.
직접 공부해서 다음 글로 정리해 보려고 합니다.