Qt는 왜 신호와 슬롯에 Moc을 사용할까?
템플릿은 전달된 인수의 유형에 따라 컴파일러가 즉시 코드를 생성할 수 있도록 하는 C++의 내장 메커니즘입니다. 따라서 템플릿은 프레임워크 제작자들에게 매우 흥미롭고, 실제로 Qt에서는 여러 곳에서 고급 템플릿을 사용하고 있습니다. 하지만 한계가 있습니다: 템플릿으로 쉽게 표현할 수 있는 것이 있고 템플릿으로 표현할 수 없는 것이 있습니다. 일반적인 벡터 컨테이너 클래스는 포인터 타입을 부분적으로 특수화하더라도 쉽게 표현할 수 있지만, 문자열로 주어진 XML 설명을 기반으로 그래픽 사용자 인터페이스를 설정하는 함수는 템플릿으로 표현할 수 없습니다. 그리고 그 사이에는 회색 영역이 존재합니다. 코드 크기, 가독성, 이식성, 유용성, 확장성, 견고성, 궁극적으로 디자인적 아름다움을 희생하면서 템플릿으로 해킹할 수 있는 것들입니다. 템플릿과 C 전처리기는 모두 놀랍도록 똑똑하고 놀라운 일을 할 수 있도록 확장할 수 있습니다. 하지만 이러한 작업을 수행할 수 있다고 해서 반드시 그렇게 하는 것이 올바른 디자인 선택이라는 의미는 아닙니다. 안타깝게도 코드는 책으로 출판되는 것이 아니라 실제 운영 체제에서 실제 컴파일러로 컴파일되어야 합니다.
다음은 Qt에서 moc을 사용하는 몇 가지 이유입니다:
구문 문제
구문은 단순한 설탕이 아닙니다. 알고리즘을 표현하는 데 사용하는 구문은 코드의 가독성과 유지 보수성에 큰 영향을 미칠 수 있습니다. Qt의 시그널과 슬롯에 사용되는 구문은 실제로 매우 성공적인 것으로 입증되었습니다. 이 구문은 직관적이고 사용하기 쉬우며 읽기 쉽습니다. Qt를 배우는 사람들은 이 문법이 매우 추상적이고 일반적임에도 불구하고 신호와 슬롯 개념을 이해하고 활용하는 데 도움이 된다는 것을 알게 됩니다. 따라서 프로그래머는 디자인 패턴에 대해 생각할 필요도 없이 처음부터 디자인을 바로 시작할 수 있습니다.
코드 생성기가 좋다
Qt의 moc
(Meta-Object Compiler)는 컴파일된 언어의 기능을 뛰어넘는 깔끔한 방법을 제공합니다. 이는 모든 표준 C++ 컴파일러로 컴파일할 수 있는 추가 C++ 코드를 생성하여 이를 수행합니다. moc
은 C++ 소스 파일을 읽습니다. Q_OBJECT 매크로가 포함된 클래스 선언을 하나 이상 발견하면 해당 클래스의 메타 객체 코드가 포함된 또 다른 C++ 소스 파일을 생성합니다. moc
에 의해 생성된 C++ 소스 파일은 컴파일되어 클래스 구현과 링크되어야 합니다(또는 클래스의 소스 파일에 #included
이 될 수 있음). 일반적으로 moc
은 수동으로 호출하지 않고 빌드 시스템에 의해 자동으로 호출되므로 프로그래머의 추가 작업이 필요하지 않습니다.
Qt XML에서 사용하는 코드 생성기는 moc
만이 아닙니다. 또 다른 대표적인 예는 uic
(User Interface Compiler)입니다. 이 도구는 XML로 사용자 인터페이스 설명을 받아 양식을 설정하는 C++ 코드를 생성합니다. Qt 외부에서도 코드 생성기는 흔히 볼 수 있습니다. 예를 들어 rpc
및 idl
을 사용하면 프로그램이나 객체가 프로세스 또는 기계 경계를 넘어 통신할 수 있습니다. 또는 lex
및 yacc
이 가장 잘 알려진 스캐너 및 파서 생성기로, 매우 다양한 스캐너 및 파서 생성기가 있습니다. 이들은 문법 사양을 입력으로 받아 상태 머신을 구현하는 코드를 생성합니다. 코드 생성기의 대안으로는 해킹된 컴파일러, 독점 언어 또는 단방향 대화상자나 마법사가 있는 그래픽 프로그래밍 도구가 있으며, 컴파일 시간이 아닌 디자인 시간 동안 모호한 코드를 생성합니다. 유니티는 고객을 독점적인 C++ 컴파일러나 특정 통합 개발 환경에 가두지 않고 고객이 선호하는 도구를 사용할 수 있도록 지원합니다. 프로그래머가 생성된 코드를 소스 리포지토리에 추가하도록 강요하는 대신, 유니크의 정신에 따라 더 깔끔하고 안전한 도구를 빌드 시스템에 추가하도록 권장합니다.
동적인 GUI
C++는 표준화되고 강력하며 정교한 범용 언어입니다. 전체 운영 체제, 데이터베이스 서버, 고급 그래픽 애플리케이션부터 일반적인 데스크톱 애플리케이션에 이르기까지 모든 종류의 애플리케이션에 걸쳐 광범위한 소프트웨어 프로젝트에서 활용되는 유일한 언어입니다. C++의 성공 비결 중 하나는 최대 성능과 최소 메모리 소비에 초점을 맞춘 확장 가능한 언어 설계와 ANSI C 호환성을 유지한다는 점입니다.
이러한 모든 장점에도 불구하고 몇 가지 단점이 있습니다. C++의 경우 정적 객체 모델은 컴포넌트 기반 그래픽 사용자 인터페이스 프로그래밍에 있어 Objective C의 동적 메시징 방식에 비해 분명한 단점이 있습니다. 하이엔드 데이터베이스 서버나 운영 체제에 적합한 디자인이 GUI 프론트엔드에 반드시 적합한 것은 아닙니다. 저희는 moc
을 통해 이러한 단점을 장점으로 바꾸고 안전하고 효율적인 그래픽 사용자 인터페이스 프로그래밍이라는 과제를 해결하는 데 필요한 유연성을 추가했습니다.
저희의 접근 방식은 템플릿으로 할 수 있는 모든 것을 훨씬 뛰어넘습니다. 예를 들어 객체 속성을 가질 수 있습니다. 또한 과부하가 핵심 개념인 언어로 프로그래밍할 때 자연스럽게 느껴지는 오버로드된 신호와 슬롯을 가질 수 있습니다. 신호는 클래스 인스턴스의 크기에 0바이트를 추가하므로 바이너리 호환성을 깨지 않고도 새로운 신호를 추가할 수 있습니다.
또 다른 장점은 런타임에 객체의 신호와 슬롯을 탐색할 수 있다는 것입니다. 연결하려는 객체의 정확한 유형을 알 필요 없이 유형 안전 콜 바이 네임을 사용하여 연결을 설정할 수 있습니다. 템플릿 기반 솔루션에서는 불가능합니다. 이러한 런타임 인트로스펙션은 예를 들어 Qt Widgets Designer 의 XML UI 파일에서 생성 및 연결되는 GUI와 같은 새로운 가능성을 열어줍니다.
호출 성능이 전부는 아니다
Qt의 신호와 슬롯 구현은 템플릿 기반 솔루션만큼 빠르지 않습니다. 일반적인 템플릿을 구현할 때 신호를 생성하는 데 드는 비용은 대략 4번의 일반 함수 호출과 비슷하지만, Qt는 약 10번의 함수 호출과 비슷한 노력이 필요합니다. Qt 메커니즘에는 일반 마샬러, 인트로스펙션, 서로 다른 스레드 간의 대기열 호출, 궁극적으로 스크립트 가능성이 포함되어 있기 때문에 이는 놀라운 일이 아닙니다. 과도한 인라이닝과 코드 확장에 의존하지 않으며 탁월한 런타임 안전성을 제공합니다. Qt의 이터레이터는 안전하지만 더 빠른 템플릿 기반 시스템의 이터레이터는 그렇지 않습니다. 여러 수신기로 신호를 보내는 과정 중에도 프로그램 충돌 없이 수신기를 안전하게 삭제할 수 있습니다. 이러한 안전성이 없다면 결국 디버깅하기 어려운 메모리 읽기 또는 쓰기 오류로 애플리케이션이 충돌할 것입니다.
그럼에도 불구하고 템플릿 기반 솔루션이 신호와 슬롯을 사용하는 애플리케이션의 성능을 향상시킬 수는 없을까요? Qt가 신호를 통해 슬롯을 호출하는 비용에 약간의 오버헤드를 추가하는 것은 사실이지만, 호출 비용은 전체 슬롯 비용의 일부에 불과합니다. Qt의 신호와 슬롯 시스템에 대한 벤치마킹은 일반적으로 빈 슬롯을 가지고 수행됩니다. 슬롯에서 몇 가지 간단한 문자열 연산과 같은 유용한 작업을 수행하는 즉시 호출 오버헤드는 무시할 수 있는 수준이 됩니다. Qt의 시스템은 매우 최적화되어 있어서 문자열 연산이나 템플릿 컨테이너에서 무언가를 삽입/제거하는 것과 같이 연산자 새로 만들기나 삭제가 필요한 작업은 신호를 보내는 것보다 훨씬 더 많은 비용이 듭니다.
참고: 성능이 중요한 작업의 타이트한 내부 루프에 신호와 슬롯 연결이 있고 이 연결이 병목 현상으로 파악된다면 신호와 슬롯 대신 표준 리스너-인터페이스 패턴을 사용하는 것을 고려해 보세요. 이런 경우에는 어쨌든 1:1 연결만 필요할 것입니다. 예를 들어 네트워크에서 데이터를 다운로드하는 객체가 있는 경우 요청된 데이터가 도착했음을 나타내는 신호를 사용하는 것은 매우 합리적인 설계입니다. 하지만 모든 바이트를 하나씩 소비자에게 보내야 한다면 신호와 슬롯 대신 리스너 인터페이스를 사용하세요.
제한 없음
신호와 슬롯에 moc
을 사용했기 때문에 템플릿으로는 할 수 없는 다른 유용한 기능을 추가할 수 있었습니다. 여기에는 생성된 tr()
함수를 통한 범위 지정 번역과 인트로스펙션 및 확장된 런타임 유형 정보가 포함된 고급 속성 시스템이 포함됩니다. Qt Widgets Designer 같은 강력하고 일반적인 사용자 인터페이스 디자인 도구는 강력하고 내성적인 속성 시스템이 없으면 불가능하지는 않더라도 작성하기가 훨씬 더 어려울 것입니다. 하지만 여기서 끝이 아닙니다. 또한 시스템의 RTTI에 의존하지 않아 그 한계를 공유하지 않는 동적 qobject_cast<T>() 메커니즘도 제공합니다. 이를 통해 동적으로 로드된 컴포넌트의 인터페이스를 안전하게 쿼리할 수 있습니다. 또 다른 애플리케이션 영역은 동적 메타 객체입니다. 예를 들어 ActiveX 컴포넌트를 가져와서 런타임에 메타 객체를 생성할 수 있습니다. 또는 메타 객체를 내보내서 Qt 컴포넌트를 ActiveX 컴포넌트로 내보낼 수도 있습니다. 템플릿으로는 이 두 가지를 모두 할 수 없습니다.
moc
C++는 기본적으로 C++ 고유의 성능과 확장성 이점을 유지하면서 Objective-C나 Java 런타임 환경의 유연성을 제공합니다. 이것이 바로 오늘날 Qt를 유연하고 편안한 도구로 만든 원동력입니다.
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.