在CRTP中,我想将构造函数干净地注入派生类 - 不使用宏并且不写出来。这似乎是不可能的,所以我想出了一些解决方法。
首先,有一个底层事件类 (QEvent),每个派生类都应该有一个唯一的整数类型标记(请参阅基本原理)。您可以通过调用注册函数来获得它创建一个 CRTP 包装器非常容易,它将对您隐藏它:
template <typename Derived> class EventWrapper : public QEvent {
public:
EventWrapper() : QEvent(staticType()) {}
static QEvent::Type staticType() {
static QEvent::Type type = static_cast<QEvent::Type>(registerEventType());
return type;
}
};
class MyEvent1 : public EventWrapper<MyEvent1> {}; // easy-peasy
class MyEvent2 : public EventWrapper<MyEvent2> {};
请注意MyEvent1::staticType() != MyEvent2::staticType()
:registerEventType()
每次调用时都会返回唯一类型。
现在我希望事件类携带一些数据:
template <typename Derived> class StringEvent : public EventWrapper<D> {
std::string m_str;
public:
explicit StringEvent(const std::string & str) : m_str(str) {}
std::string value() const { return m_str; }
};
但是在这里我们遇到了一个问题:我们需要在每个派生类中手动定义构造函数。这里的重点是创建这样的类应该很容易,因为可能有许多不同的带有字符串的事件类型。但这绝非易事:
class MyEvent3 : public StringEvent<MyEvent3> {
public: MyEvent3(std::string s) : StringEvent(s) {}
};
这显然会很快变老,即使使用 C++11 构造函数转发:
class MyEvent3 : public StringEvent<MyEvent3> { using StringEvent::StringEvent; };
我们想要的是一种将这个构造函数注入派生类的方法,或者避免这样做,同时仍然提供易用性。当然,您可以将其隐藏在预处理器宏中,但我讨厌这些宏,因为它们为非常简单的概念引入了新名称,所以维护起来很麻烦。
我们当然可以使用虚拟类型。请注意,不需要定义虚拟类型。它只是用作类型参数的名称。
// Pre-C++11
class DummyEvent3;
typedef StringEvent<DummyEvent3> MyEvent3;
// C++11
class DummyEvent3;
using MyEvent3 = StringEvent<DummyEvent3>;
另一种解决方案是使用int
模板参数并使用枚举值,但这会带回registerEventType()
最初通过使用解决的唯一性问题。保证一个大程序是正确的并不好玩。而且你仍然需要拼出枚举。
因此,我提出了一个元程序类,我将其称为元工厂,它可以为我们生成现成的StringEvent
类,同时将其全部保留为一个类型定义:
// the metafactory for string events
template <typename Derived> class StringEventMF {
public:
class Event : public EventWrapper<Derived> {
std::string m_str;
public:
explicit Event(const std::string & val) : m_str(val) {}
std::string value() const { return m_str; }
};
};
或者干脆
template <typename Derived> class StringEventMF {
public:
typedef StringEvent<Derived> Event;
};
这像这样使用:
class Update : public StringEventMF<Update> {};
class Clear : public StringEventMF<Clear> {};
void test() {
Update::Event * ev = new Update::Event("foo");
...
}
您使用的类是Update::Event
, Clear::Event
。和是Update
元Clear
工厂:它们为我们生成所需的事件类。从元工厂派生回避了从具体类类型派生。元工厂类型提供了创建唯一具体类类型所需的唯一类型鉴别器。
问题是:
有没有“更清洁”或“更理想”的方法?理想情况下,以下非工作伪代码将是我理想的实现方式 - 零重复:
class UpdateEvent : public StringEvent <magic>;
派生类的名称只出现一次,基本概念的名称也
StringEvent
只出现一次。CRTP 要求类名出现两次 - 到目前为止,我认为这是可以接受的,但我的元编程功能已经破烂。再一次,我想要一个无预处理器的解决方案,否则它会很容易。Metafactory这个名字是我最初的发明(哈哈),还是仅仅是我缺少的 google-Fu?这种元工厂模式似乎非常灵活。通过多重推导很容易组成元工厂。假设您想要一个
Update::Event
由一家工厂Update::Foo
制造,并由另一家工厂制造。
这个问题是由这个答案引起的。注意:在实际代码中我将使用QString
,但我试图使其尽可能通用。