如何向后移植 Qt 中的功能?
How to backport functionality in Qt?
假设有一个项目使用 Qt,并且依赖于新版本 Qt 中的功能(例如添加 class 成员)。该项目旨在使用 Qt 的 "system"/分发版本构建,该版本可能早于项目所依赖的版本。
天真的方法导致预处理器地狱:
void Class::foo() {
#if QT_VERSION >= QT_VERSION_CHECK(...)
QClass::newFangled();
#else
QClass::oldFangled1();
blurble();
#endif
}
是否有更简洁的方法,在使用该功能时不依赖于预处理器宏? IE。我们不想要:
// THIS IS BLAH
#if QT_VERSION >= QT_VERSION_CHECK(...)
#define QClass_newFangled QClass::newFangled
#else
#define QClass_newFangled [this]{ \
QClass::oldFangled1(); \
blurble(); \
}
#endif
void Class::foo() {
QClass_newFangled();
}
一种可行的方法是在自定义命名空间中使用给定的 class 或全局函数。该命名空间中的 class/function 可以从 Qt 导入而无需更改,或者是向后移植所需功能的自定义名称。
下面的代码是 Qt 命名空间感知的:它将与命名空间 Qt 构建一起工作。
在使用时,向后移植的功能(全局函数和 classes)在 compat
命名空间中访问,即
void test() {
...
qDebug() << compat::qEnvironmentVariableIntValue("RUNMODE");
compat::QTimer::singleShot(1000, object, []{ ... });
}
请注意,将带有默认参数的 functions/methods 添加到 compat
命名空间时,默认参数仅在 C++17 及更高版本中传播。否则,我们必须通过定义转发来自己处理它们 functions/methods.
向后移植一个全局函数
假设我们想从 Qt 5.5 向后移植 qEnvironmentVariableIntValue
:
// INTERFACE
namespace compat {
#if QT_VERSION >= QT_VERSION_CHECK(5,5,0)
#if __cplusplus >= 201703L
using QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue); // C++17
#else
int inline qEnvironmentVariableIntValue(const char *varName, bool *ok = {}) {
return QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue)(varName, ok);
}
#endif
#else
int qEnvironmentVariableIntValue(const char *varName, bool *ok = {});
#endif
} // compat
// IMPLEMENTATION
namespace compat {
#if QT_VERSION < QT_VERSION_CHECK(5,5,0)
using QT_PREPEND_NAMESPACE(qgetenv); // C++17
int qEnvironmentVariableIntValue(const char *varName, bool *ok) {
return qgetenv(varName).toInt(ok, 0);
}
#endif // Qt<5.5
} // compat
反向移植到 class
假设我们想要向后移植 Qt 5.4 的 QTimer::singleShot(int, QObject*, Functor)
,其动机是 this answer:
// INTERFACE
namespace compat {
#if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
using QT_PREPEND_NAMESPACE(QTimer);
#else
using Q_QTimer = QT_PREPEND_NAMESPACE(QTimer);
class QTimer : public Q_QTimer {
Q_OBJECT
public:
#if __cplusplus >= 201703L
using Q_QTimer::Q_QTimer; // C++17
#else
QTimer(QObject *parent = {}) : Q_QTimer(parent) {}
#endif
template <class Functor> static void singleShot(int, QObject *, Functor &&);
};
template <class Functor>
void QTimer::singleShot(int msec, QObject *context, Functor &&fun) {
...
}
#endif
} // compat
假设有一个项目使用 Qt,并且依赖于新版本 Qt 中的功能(例如添加 class 成员)。该项目旨在使用 Qt 的 "system"/分发版本构建,该版本可能早于项目所依赖的版本。
天真的方法导致预处理器地狱:
void Class::foo() {
#if QT_VERSION >= QT_VERSION_CHECK(...)
QClass::newFangled();
#else
QClass::oldFangled1();
blurble();
#endif
}
是否有更简洁的方法,在使用该功能时不依赖于预处理器宏? IE。我们不想要:
// THIS IS BLAH
#if QT_VERSION >= QT_VERSION_CHECK(...)
#define QClass_newFangled QClass::newFangled
#else
#define QClass_newFangled [this]{ \
QClass::oldFangled1(); \
blurble(); \
}
#endif
void Class::foo() {
QClass_newFangled();
}
一种可行的方法是在自定义命名空间中使用给定的 class 或全局函数。该命名空间中的 class/function 可以从 Qt 导入而无需更改,或者是向后移植所需功能的自定义名称。
下面的代码是 Qt 命名空间感知的:它将与命名空间 Qt 构建一起工作。
在使用时,向后移植的功能(全局函数和 classes)在 compat
命名空间中访问,即
void test() {
...
qDebug() << compat::qEnvironmentVariableIntValue("RUNMODE");
compat::QTimer::singleShot(1000, object, []{ ... });
}
请注意,将带有默认参数的 functions/methods 添加到 compat
命名空间时,默认参数仅在 C++17 及更高版本中传播。否则,我们必须通过定义转发来自己处理它们 functions/methods.
向后移植一个全局函数
假设我们想从 Qt 5.5 向后移植 qEnvironmentVariableIntValue
:
// INTERFACE
namespace compat {
#if QT_VERSION >= QT_VERSION_CHECK(5,5,0)
#if __cplusplus >= 201703L
using QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue); // C++17
#else
int inline qEnvironmentVariableIntValue(const char *varName, bool *ok = {}) {
return QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue)(varName, ok);
}
#endif
#else
int qEnvironmentVariableIntValue(const char *varName, bool *ok = {});
#endif
} // compat
// IMPLEMENTATION
namespace compat {
#if QT_VERSION < QT_VERSION_CHECK(5,5,0)
using QT_PREPEND_NAMESPACE(qgetenv); // C++17
int qEnvironmentVariableIntValue(const char *varName, bool *ok) {
return qgetenv(varName).toInt(ok, 0);
}
#endif // Qt<5.5
} // compat
反向移植到 class
假设我们想要向后移植 Qt 5.4 的 QTimer::singleShot(int, QObject*, Functor)
,其动机是 this answer:
// INTERFACE
namespace compat {
#if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
using QT_PREPEND_NAMESPACE(QTimer);
#else
using Q_QTimer = QT_PREPEND_NAMESPACE(QTimer);
class QTimer : public Q_QTimer {
Q_OBJECT
public:
#if __cplusplus >= 201703L
using Q_QTimer::Q_QTimer; // C++17
#else
QTimer(QObject *parent = {}) : Q_QTimer(parent) {}
#endif
template <class Functor> static void singleShot(int, QObject *, Functor &&);
};
template <class Functor>
void QTimer::singleShot(int msec, QObject *context, Functor &&fun) {
...
}
#endif
} // compat