Qt moc مع التطبيقات داخل ملفات الرأس؟
سؤال
هل من الممكن إخبار Qt MOC بأنني أرغب في الإعلان عن الفصل وتنفيذه في ملف واحد بدلاً من تقسيمهما إلى ملف .h و.cpp؟
المحلول 4
أعتقد أن هذا هو أفضل طريقة. إنها في الواقع كيف أقوم ببناء كل الأشياء الخاصة بي الآن.
كيو تي 4.8.7
Works.Pro:
SOURCES += \
main.cpp
HEADERS += \
Window.h \
MyWidget.h
Main.cpp
#include <QtGui>
#include "Window.h"
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
Window window;
window.show();
return app.exec();
}
Window.H
#ifndef WINDOW_H
#define WINDOW_H
#include <QtGui>
#include "MyWidget.h"
class Window : public QWidget
{
Q_OBJECT
private:
MyWidget *whatever;
public:
Window()
{
QHBoxLayout *layout = new QHBoxLayout;
setLayout(layout);
whatever = new MyWidget("Screw You");
layout->addWidget(whatever);
}
};
#include "moc_Window.cpp"
#endif // WINDOW_H
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QtGui>
class MyWidget : public QLabel
{
Q_OBJECT
public:
MyWidget(QString text) : QLabel(text)
{
// Whatever
}
};
#include "moc_MyWidget.cpp"
#endif // MYWIDGET_H
بناء ... Qmake Works.Pro
صنع
نصائح أخرى
إذا كنت ترغب في إعلان وتنفيذ فئة فرعية QOBject في ملف CPP ، فيجب عليك تضمين ملف MOC يدويًا.
على سبيل المثال: (ملف main.cpp)
struct SubObject : QObject
{
Q_OBJECT
};
//...
#include "main.moc"
عليك إعادة تشغيل MOC (make qmake
) بعد إضافة #include
بيان.
ليرة تركية؛ د
نعم، إذا كنت تتحدث فقط عن الملفات التي تكتبها بنفسك (على عكس تلك التي تم إنشاؤها بواسطة moc).لا تحتاج إلى القيام بأي شيء خاص على الإطلاق.
إذا كنت ترغب في تضمين مخرجات moc بشكل صريح في الملفات التي تكتبها، فهناك حالة واحدة يمكنك فيها ذلك يجب افعل ذلك، وحالة واحدة حيث أنت قد ترغب في القيام بذلك.لنفترض ذلك MyObject
تم الإعلان عن الطبقة في MyObject.h
ويرد تعريفك له في MyObject.cpp
:
MyObject.moc
لا بد وأن متضمنة .في نهايةالمطاف لMyObject.cpp
إذا تعلن أيQ_OBJECT
الطبقات داخلMyObject.cpp
.moc_MyObject.cpp
يمكن ان يكون متضمنة في أى مكان فيMyObject.cpp
لخفض عدد وحدات الترجمة في مشروعك إلى النصف.إنه تحسين وقت البناء فقط.إذا لم تفعل ذلك،moc_MyObject.cpp
سيتم تجميعها بشكل منفصل.
في كل مرة تقوم بإضافة أو إزالة Q_OBJECT
ماكرو من أي مصدر أو ملف رأس، أو قمت بإضافة أو إزالة تضمينات صريحة لمخرجات moc في مثل هذه الملفات، فيجب عليك إعادة تشغيل qmake/cmake.
لإعادة تشغيل qmake/cmake في Qt Creator، ما عليك سوى النقر بزر الماوس الأيمن على مشروع المستوى الأعلى واختيار قم بتشغيل كميك أو قم بتشغيل cmake من قائمة السياق.
الجواب البسيط
قد يتكون مثال لمشروع Qt القائم على qmake من ثلاثة ملفات، على النحو التالي:
# test.pro
QT += core
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
HEADERS += myobject.h
// main.cpp
#include "myobject.h"
int main() {
MyObject obj;
obj.staticMetaObject; // refer to a value defined in moc output
return 0;
}
// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
class MyObject : public QObject {
Q_OBJECT
public:
MyObject() {}
Q_SLOT void aSlot() {}
};
#endif // MYOBJECT_H
إنه لا يفعل الكثير، لكنه صحيح بالتأكيد.بصرف النظر عن المكتبات المتنوعة التي يربط نظام البناء بها مشروعنا، هناك مكتبتان خاصتان بالمشروع وحدات الترجمة: main.cpp
و moc_myobject.cpp
.
على الرغم من أنه يبدو أن التنفيذ الكامل لـ MyObject
موجود في ملف الرأس، وهو ليس كذلك حقًا.ال Q_OBJECT
يعلن الماكرو عن بعض أجزاء التنفيذ التي قد تكون غير محددة، لولا التعريفات التي تم إنشاؤها بواسطة moc.
كيف يدخل moc في الصورة؟عندما يتم إنشاء المشروع لأول مرة، تقوم أداة metabuild - إما qmake أو cmake - بفحص كافة ملفات الإدخال C++ بحثًا عن وجود Q_OBJECT
دقيق.تلك التي تحتوي عليه، يتم منحها معاملة خاصة.في هذا المشروع المثال، myobject.h
يتضمن Q_OBJECT
ويتم معالجتها من خلال moc إلى moc_myobject.cpp
.تتم إضافة الأخير إلى قائمة المصادر التي تم تجميعها بواسطة مترجم C++.إنه، من الناحية المفاهيمية فقط، كما لو كان لديك SOURCES += moc_myobject.cpp
في ال .pro
ملف. بالطبع لا يجب عليك أبدًا إضافة مثل هذا السطر إلى ملف .pro.
الآن لاحظ أن كامل بداية شئ MyObject
يقع في ملفين: myobject.h
و moc_myobject.cpp
. myobject.h
يمكن تضمينها في أي عدد تريده من وحدات الترجمة - لأنه لا توجد تعريفات خارج التصنيف (مستقلة) من شأنها أن تنتهك قاعدة التعريف الواحدة.يعامل نظام البناء moc_myobject.cpp
كوحدة ترجمة واحدة ومنفصلة - يتم الاهتمام بكل هذا من أجلك.
وبذلك يتم الوصول إلى هدفك دون أي جهد على الإطلاق:ليس لديك أي شيء خاص لتفعله لوضع التنفيذ الكامل لـ MyObject
- حفظ البتات التي ينتجها moc - في ملف الرأس.قد يؤدي ذلك إلى إطالة أوقات التجميع، ولكنه غير ضار بخلاف ذلك.
نهج ينتهك القواعد
إنه ينتهك قاعدة تعريف واحدة, ، على وجه الدقة، وبالتالي ينتج برنامج C++ غير صالح.
الآن قد تفكر في أن تصبح "ذكيًا" وأن تقوم بتضمين مخرجات moc بالقوة في ملف الرأس.سيكون qmake/cmake/qbs ملائمًا، وسيكتشف ذلك ولن يقوم بمعالجة إخراج moc بشكل منفصل من خلال المترجم بعد الآن، كما فعلت ذلك بالفعل.
لذا، لنفترض أنك تغيرت في المشروع أعلاه myobject.h
لقراءتها على النحو التالي:
// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
class MyObject : public QObject {
Q_OBJECT
public:
MyObject() {}
Q_SLOT void aSlot() {}
};
#include "moc_myobject.cpp"
#endif // MYOBJECT_H
في الوضع الحالي، سيستمر المشروع في التجميع، ويبدو أنه يحقق هدفك المتمثل في الحصول على ملف واحد فقط يحدد كامل الملف MyObject
- الأجزاء التي كتبتها، والأجزاء التي أنشأها moc، كلاهما.ولكن هذا فقط بسبب ظرف سعيد غير محتمل:محتويات moc_*.cpp
لا تزال في وحدة ترجمة واحدة فقط -
لنفترض الآن أننا أضفنا ملف مصدر ثانيًا إلى مشروعنا:
# test.pro
QT += core
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp test.cpp
HEADERS += myobject.h
// test.cpp
#include "myobject.h"
ليس كثيرا لذلك.يجب أن تعمل، حتى لو لم تفعل الكثير، أليس كذلك؟
للأسف، لن يتم الارتباط.الآن محتويات moc_myobject.cpp
هي جزء من وحدتين للترجمة.منذ moc_myobject.cpp
إن الدواخل الخاصة بـ مليئة بتعريفات أعضاء الفصل المستقلين، وهذا ينتهك قواعد قاعدة تعريف واحدة.تنص القاعدة على أن التعريفات المستقلة قد تظهر فقط في وحدة ترجمة واحدة ضمن هدف.الرابط، كونه حارس هذه القاعدة، يشكو بحق.
حول تضمين مخرجات moc في ملف .cpp
كما تمت الإشارة إليه في TL;DR، لا يمنع أي مما سبق التضمين الصريح لمخرجات moc في ملفات المصدر (.cpp)، في ظروف محددة.
بالنظر إلى "foo.h" و"foo.cpp"، والمشروع الذي تتم إدارته بواسطة qmake أو cmake، فإن نظام البناء سيوجه moc
لتوليد ما يصل إلى اثنين من المخرجات:
moc_foo.cpp
منfoo.h
, إذاfoo.h
يتضمنQ_OBJECT
دقيق.foo.moc
منfoo.cpp
, إذاfoo.cpp
يتضمن#include "foo.moc"
.
دعونا نفحص بالتفصيل سبب رغبتك في تضمين أي منهما في ملف .cpp.
بما في ذلك xxx.moc
في بعض الأحيان، وخاصة في الأيام التي سبقت C++ 11 وQt 5، يكون من المفيد الإعلان عن فئات QObject المساعدة الصغيرة للاستخدام المحلي ضمن وحدة ترجمة واحدة (الملف المصدر) فقط.
يكون هذا مفيدًا أيضًا عند كتابة حالات اختبار قائمة بذاتها وأمثلة لاستخدام Stackoverflow في ملف واحد.
لنفترض أنك ترغب في أن يوضح شخص ما، في ملف واحد، كيفية استدعاء فتحة من حلقة الحدث:
// main.cpp
#include <QCoreApplication>
#include <QTextStream>
#include <cstdio>
QTextStream out(stdout);
class MyObject : public QObject {
Q_OBJECT
public:
MyObject() {}
Q_SLOT void mySlot() { out << "Hello from " << __FUNCTION__ << endl; }
};
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
MyObject obj;
QMetaObject::invokeMethod(&obj, Qt::QueuedConnection, "mySlot");
QMetaObject::invokeMethod(&app, Qt::QueuedConnection, "quit");
return app.exec();
}
#include "main.moc"
منذ MyObject
هي فئة صغيرة تستخدم محليًا فقط في main.moc
, ، لن يكون من المنطقي وضع تعريفه في ملف رأس منفصل.ال #include "main.moc"
سيتم ملاحظة الخط بواسطة qmake/cmake و main.cpp
سيتم تغذيتها من خلال moc، مما يؤدي إلى main.moc
.منذ main.moc
يحدد أعضاء MyObject
, ، يجب أن يتم تضمينه في مكان ما MyObject
أعلن.كما هو داخل الإعلان main.cpp
, ، لا يمكنك الحصول عليه main.moc
تكون وحدة ترجمة منفصلة:لن يتم تجميعها بسبب MyObject
لم يتم الإعلان عنها.المكان الوحيد الذي يتم الإعلان عنه هو في الداخل main.cpp
, ، في مكان ما قرب النهاية.لهذا السبب، يعد تضمينه دائمًا رهانًا آمنًا foo.moc
في نهاية foo.cpp
.
والآن يسأل القارئ الذكي:كيف ذلك moc_foo.cpp
يحصل على إعلانات الطبقات التي يحدد أعضائها؟بكل بساطة:يتضمن بشكل صريح ملف الرأس الذي تم إنشاؤه منه (هنا: foo.h
).بالطبع foo.moc
لا أستطيع فعل ذلك، لأنه سيكسر قاعدة التعريف الفردية عن طريق مضاعفة تعريف كل شيء foo.cpp
.
بما في ذلك moc_xxx.cpp
في مشاريع Qt الكبيرة بشكل خاص، قد يكون لديك - في المتوسط - ملفان و وحدتا ترجمة لكل فصل:
MyObject.h
وMyObject.cpp
هي الملفات التي تكتبها.MyObject.cpp
وmoc_MyObject.cpp
هي وحدات الترجمة
من الممكن خفض عدد وحدات الترجمة إلى النصف عن طريق التضمين بشكل صريح moc_MyObject.cpp
في مكان ما MyObject.cpp
:
// MyObject.cpp
#include "MyObject.h"
#include "moc_MyObject.cpp"
...
أعتقد أنه يمكنك عادة الإعلان وتنفيذ الفصل في ملف الرأس دون استخدام أي شيء خاص ، على سبيل المثال:
#include <QObject>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject * parent)
{
// Constructor Content
}
methodExample()
{
// Method content
}
};
بعد ذلك يمكنك إضافة ملف الرأس إلى ملف PRI وتنفيذ QMake مرة أخرى وهذا كل شيء. لديك فئة ترث من Qobject ويتم تنفيذها وإعلانها.