具有特定于子类的模板化参数类型的 Qt SIGNAL 架构

发布于 2024-08-25 09:57:23 字数 1978 浏览 3 评论 0 原文

我正在使用 Qt 开发科学数据采集应用程序。由于我不是 Qt 方面的资深专家,因此我希望社区就以下问题提供一些架构建议:

该应用程序支持多个硬件采集接口,但我想在这些接口之上提供一个通用的 API。每个接口都有一个示例数据类型及其数据单位。因此,我将每个设备的样本向量表示为 Boost.Units 数量的 std::vector (即 std::vector>)。我想使用多播样式架构,其中每个数据源将新收到的数据广播给 1 个或多个感兴趣的各方。 Qt 的 Signal/Slot 机制显然适合这种风格。因此,我希望每个数据源发出一个信号,例如

typedef std::vector<boost::units::quantity<unit,sample_type> > SampleVector
signals:
    void samplesAcquired(SampleVector sampleVector);

适合该设备的单位和样本类型。由于元对象编译器不支持临时 QObject 子类,因此似乎没有办法为所有定义了 samplesAcquired 的数据源提供(临时)基类。信号。换句话说,以下不会工作:

template<T,U> //sample type and units
class DataSource : public QObject {
  Q_OBJECT
  ...
  public:
    typedef std::vector<boost::units::quantity<U,T> > SampleVector
  signals:
    void samplesAcquired(SampleVector sampleVector);
};

我能想到的最佳选择是两层方法:

template<T,U> //sample type and units
class IAcquiredSamples {
    public:
        typedef std::vector<boost::units::quantity<U,T> > SampleVector
        virtual shared_ptr<SampleVector> acquiredData(TimeStamp ts, unsigned long nsamples);
};

class DataSource : public QObject {
    ...
    signals:
      void samplesAcquired(TimeStamp ts, unsigned long nsamples);
};

samplesAcquired信号现在给出一个采集的时间戳和样本数量,客户端必须使用 IAcquiredSamples API 来检索这些样本。显然,数据源必须对 DataSource 和 IAcquiredSamples 进行子类化。

这种方法的缺点似乎是 API 失去了简单性……如果客户端能够在连接的插槽中获取获取的样本,那就更好了。能够使用 Qt 的排队连接也将使线程问题变得更容易,而不必在每个子类中的 acquiredData 方法中管理它们。

另一种可能性是使用 QVariant 参数。这必然使子类有责任使用 Q_REGISTER_METATYPE/qRegisterMetaType 注册其特定的样本向量类型。没什么大不了的。然而,基类的客户端将无法知道 QVariant 值类型是什么类型,除非标记结构也随信号一起传递。我认为这个解决方案至少和上面的解决方案一样复杂,因为它迫使抽象基类 API 的客户端处理类型系统的一些更棘手的方面。

那么,有没有办法实现信号参数的模板化呢?有没有比我提出的架构更好的架构?

I am developing a scientific data acquisition application using Qt. Since I'm not a deep expert in Qt, I'd like some architecture advise from the community on the following problem:

The application supports several hardware acquisition interfaces but I would like to provide an common API on top of those interfaces. Each interface has a sample data type and a units for its data. So I'm representing a vector of samples from each device as a std::vector of Boost.Units quantities (i.e. std::vector<boost::units::quantity<unit,sample_type> >). I'd like to use a multi-cast style architecture, where each data source broadcasts newly received data to 1 or more interested parties. Qt's Signal/Slot mechanism is an obvious fit for this style. So, I'd like each data source to emit a signal like

typedef std::vector<boost::units::quantity<unit,sample_type> > SampleVector
signals:
    void samplesAcquired(SampleVector sampleVector);

for the unit and sample_type appropriate for that device. Since tempalted QObject subclasses aren't supported by the meta-object compiler, there doesn't seem to be a way to have a (tempalted) base class for all data sources which defines the samplesAcquired Signal. In other words, the following won't work:

template<T,U> //sample type and units
class DataSource : public QObject {
  Q_OBJECT
  ...
  public:
    typedef std::vector<boost::units::quantity<U,T> > SampleVector
  signals:
    void samplesAcquired(SampleVector sampleVector);
};

The best option I've been able to come up with is a two-layered approach:

template<T,U> //sample type and units
class IAcquiredSamples {
    public:
        typedef std::vector<boost::units::quantity<U,T> > SampleVector
        virtual shared_ptr<SampleVector> acquiredData(TimeStamp ts, unsigned long nsamples);
};

class DataSource : public QObject {
    ...
    signals:
      void samplesAcquired(TimeStamp ts, unsigned long nsamples);
};

The samplesAcquired signal now gives a timestamp and number of samples for the acquisition and clients must use the IAcquiredSamples API to retrieve those samples. Obviously data sources must subclass both DataSource and IAcquiredSamples.

The disadvantage of this approach appears to be a loss of simplicity in the API... it would be much nicer if clients could get the acquired samples in the Slot connected. Being able to use Qt's queued connections would also make threading issues easier instead of having to manage them in the acquiredData method within each subclass.

One other possibility, is to use a QVariant argument. This necessarily puts the onus on subclass to register their particular sample vector type with Q_REGISTER_METATYPE/qRegisterMetaType. Not really a big deal. Clients of the base class however, will have no way of knowing what type the QVariant value type is, unless a tag struct is also passed with the signal. I consider this solution at least as convoluted as the one above, as it forces clients of the abstract base class API to deal with some of the gnarlier aspects of type system.

So, is there a way to achieve the templated signal parameter? Is there a better architecture than the one I've proposed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

地狱即天堂 2024-09-01 09:57:23

有 QVariant 类型——您可以在其上创建自定义子类型
并将其用作信号中的参数(如果我理解你的权利,这就是你想要的)
http://doc.trolltech.com/qq/qq14-metatypes.html#自定义类型inqvariant

There is QVariant type -- you can create your custom sub-type on it
and use it as parameter (if I understand your right and that's what you want) in signals
http://doc.trolltech.com/qq/qq14-metatypes.html#customtypesinqvariant

南笙 2024-09-01 09:57:23

对两层方法的一种简化是将 QObject 类作为类模板的非模板化基础,即类似

class DataSourceBase : public QObject {
    Q_OBJECT
    ...
    signals:
      void samplesAcquired(TimeStamp ts, unsigned long nsamples);
};

template<T,U> //sample type and units
class DataSource : public DataSourceBase {
    public:
        typedef std::vector<boost::units::quantity<U,T> > SampleVector
        virtual shared_ptr<SampleVector> acquiredData(TimeStamp ts, unsigned long nsamples);
};

注意,这种方法的缺点是,因为您不能使用 <类模板中的 code>Q_OBJECT 宏在 Qt 的元对象系统中没有关于它的信息。

One simplification to your two-layered approach would be to have the QObject class as a non-templated base for the class template i.e. something like

class DataSourceBase : public QObject {
    Q_OBJECT
    ...
    signals:
      void samplesAcquired(TimeStamp ts, unsigned long nsamples);
};

template<T,U> //sample type and units
class DataSource : public DataSourceBase {
    public:
        typedef std::vector<boost::units::quantity<U,T> > SampleVector
        virtual shared_ptr<SampleVector> acquiredData(TimeStamp ts, unsigned long nsamples);
};

Note that the drawback to this approach is that since you cannot use the Q_OBJECT macro in the class template there is no information about it in Qt's meta-object system.

温柔一刀 2024-09-01 09:57:23

Qt 不喜欢从 QObject 继承的类模板。如果您不需要 QObject 的运行时自省,您可能需要使用 Boost.Signals 代替,它没有这个问题。

不过,在 Qt 项目中引入 Boost.Signals 库可能有点具有挑战性。在 Boost.Signals 中,signals 是一个命名空间,而 Qt #definesignalprotected。在引入 Boost.Signals 之前,您应该确保您的 Qt 项目使用定义的 QT_NO_KEYWORDS 进行编译(qmake 中的 CONFIG += no_keywords)。

Qt doesn't like class templates that inherit from QObject. If you don't need the runtime introspection of QObject, you might want to use Boost.Signals instead, which doesn't have this problem.

Introducing the Boost.Signals library in a Qt project can be a bit challenging, though. In Boost.Signals, signals is a namespace while Qt #defines signal to protected. You should make sure your Qt project compiles with QT_NO_KEYWORDS defined (CONFIG += no_keywords in qmake), before introducing Boost.Signals.

一袭水袖舞倾城 2024-09-01 09:57:23

实际上,您可以将 Qt 类与模板一起使用,而无需对代码进行少量修改。

Qt 类和模板的问题在于生成元对象信息的 moc 工具,它根本不了解模板,但生成的代码不需要需要来自 Moc。

您可以使用 Verdigris 创建 C++/QObject 类,使用模板没有任何问题,绕过此类代码的 moc 步骤。

You actually can use Qt classes with Templates with little modifications to your code.

The issue with Qt classes and Templates is the moc tool that generates the meta object information, it has no knowledge on templates at all, but the generated code doesn't needs to come from Moc.

you can use Verdigris to create your C++/QObject class that will work with templates without any issues, bypassing the moc step for such code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文