在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> {};
Run Code Online (Sandbox Code Playgroud)
请注意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; }
};
Run Code Online (Sandbox Code Playgroud)
但是在这里我们遇到了一个问题:我们需要在每个派生类中手动定义构造函数.这里的重点是创建这样的类应该很容易,因为可能有许多不同的字符串携带事件类型.但这简直容易:
class MyEvent3 : public StringEvent<MyEvent3> {
public: MyEvent3(std::string s) : StringEvent(s) {}
};
Run Code Online (Sandbox Code Playgroud)
即使使用C++ 11构造函数转发,这显然也很快变旧:
class MyEvent3 : public StringEvent<MyEvent3> { using StringEvent::StringEvent; };
Run Code Online (Sandbox Code Playgroud)
我们想要的是一种将这个构造函数注入到派生类中的方法,或者在提供易用性的同时避免这样做.当然你可以将它隐藏在预处理器宏中,但我讨厌那些宏,它们是一种维护痛苦,因为它们为非常简单的概念引入了新的名称.
我们当然可以使用虚拟类型.请注意,不需要虚拟类型的定义.它只是一个用作类型参数的名称.
// Pre-C++11
class DummyEvent3;
typedef StringEvent<DummyEvent3> MyEvent3;
// C++11
class DummyEvent3;
using MyEvent3 = StringEvent<DummyEvent3>;
Run Code Online (Sandbox Code Playgroud)
另一个解决方案是使用int
模板参数并使用枚举值,但这会带回通过首先使用它解决的唯一性问题registerEventType()
.保证大型程序是正确的并不是一件好事.你仍然需要拼出枚举.
所以,我想出了一个metaprogram类,我将其称为metafactory,它可以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; }
};
};
Run Code Online (Sandbox Code Playgroud)
或者干脆
template <typename Derived> class StringEventMF {
public:
typedef StringEvent<Derived> Event;
};
Run Code Online (Sandbox Code Playgroud)
使用方式如下:
class Update : public StringEventMF<Update> {};
class Clear : public StringEventMF<Clear> {};
void test() {
Update::Event * ev = new Update::Event("foo");
...
}
Run Code Online (Sandbox Code Playgroud)
你使用的课程是Update::Event
,Clear::Event
.这是Update
和Clear
metafactories:它们为我们生成了所需的事件类.metafactory的推导避开了具体类类型的推导.metafactory类型给出了创建唯一具体类类型所需的唯一类型鉴别器.
问题是:
这样做有"更清洁"或"更理想"的方式吗?理想情况下,以下非工作伪代码将是我理想的做法 - 零重复:
class UpdateEvent : public StringEvent <magic>;
Run Code Online (Sandbox Code Playgroud)
派生类的名称StringEvent
只出现一次,基本概念的名称也只出现一次.CRTP要求类名出现两次 - 到目前为止我认为它是可以接受的,但我的元编程功能已经破败了.同样,我想要一个无预处理器的解决方案,否则它将是一个明智的选择.
名称metafactory是我的原始发明(哈哈),还是仅仅是我缺乏的谷歌?这种元素模式似乎非常灵活.通过多次推导可以很容易地组合元形式.假设你想要Update::Event
一个工厂生产,Update::Foo
另一个工厂制造.
这个问题是由这个答案推动的.注意:在实际代码中我会使用QString
,但我试图尽可能保持通用.
Yochai Timmer 提出了一种解决该问题的替代方法。他不必从数据载体类转发构造函数,而是公开一个生成伪派生类的工厂方法。由于它会调用未定义的行为,因此我并不是特别热衷于它。
对原始元工厂概念进行一些扩展,可以创建通用元工厂,该通用元工厂可用于创建包装“任何”数据携带类的独特事件类型。
C++11 的方法使用构造函数转发,以便可以使用普通的非模板数据载体类。C++98 的方法需要模板化的数据载体类,并且在内部需要更多的体操,但它也有效。
事件类不能进一步从 派生。这是必要的,因为派生类都将共享 的值staticType
,而这是不允许的,正如 DyP 在评论中适当指出的那样。
要测试代码,您需要事件包装器、为 C++ 变体选择的元工厂和数据载体以及测试/使用部分。
无论哪种情况,为事件生成唯一静态类型值的基本事件包装 CRTP 类是:
// A type-identifier-generating wrapper for events. It also works with RTTI disabled.
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;
}
static bool is(const QEvent * ev) { return ev->type() == staticType(); }
static Derived* cast(QEvent * ev) { return is(ev) ? static_cast<Derived*>(ev) : 0; }
};
Run Code Online (Sandbox Code Playgroud)
请注意,它还提供了强制转换为派生的方法。您可以在事件处理程序中使用它,并给出指向基本事件类的指针:
void MyClass::customEvent(QEvent* event) {
if (MyEvent::is(event)) {
auto myEvent = MyEvent::cast(event);
// use myEvent to access data carrying members etc)
}
}
Run Code Online (Sandbox Code Playgroud)
这Carrier
是参数化的数据载体类,如下所示StringData
。
// The generic event metafactory
template <typename Derived, template <typename> class Carrier> class EventMF {
class EventFwd;
class Final;
class FinalWrapper : public EventWrapper<EventFwd>, public virtual Final {};
public:
// EventFwd is a class derived from Event. The EventWrapper's cast()
// will cast to a covariant return type - the derived class. That's OK.
typedef Carrier<FinalWrapper> Event;
private:
class EventFwd : public Event {};
class Final {
friend class FinalWrapper;
friend class Carrier<FinalWrapper>;
private:
Final() {}
Final(const Final &) {}
};
};
Run Code Online (Sandbox Code Playgroud)
需要该类EventFwd
,以便我们可以将一些合理的内容EventWrapper
作为派生类传递给模板,以便cast()
静态方法可以工作。之所以FinalWrapper
存在,是因为在 C++11 之前的版本中,我们无法对类型转换进行友元转换。
现在介绍参数化数据载体。除了需要参数化基类之外,它与下面的 C++11 变体相同。
// A string carrier
template <typename Base> class StringData : public Base {
QString m_str;
public:
explicit StringData(const QString & str) : m_str(str) {}
QString value() const { return m_str; }
};
Run Code Online (Sandbox Code Playgroud)
// The generic metafactory for unique event types that carry data
template <typename Derived, class Data> class EventMF {
class Final;
EventMF();
EventMF(const EventMF &);
~EventMF();
public:
class Event : public EventWrapper<Event>, public Data, private virtual Final {
public:
template<typename... Args>
Event(Args&&... args): Data(std::forward<Args>(args)...) {}
};
private:
class Final {
friend class Event;
private:
Final() {}
Final(const Final &) {}
};
};
Run Code Online (Sandbox Code Playgroud)
具有 Final 类的前向声明的体操是存在的,因为前向声明 Event 类需要更多的打字。
数据载体非常简单:
// A string carrier
class StringData {
QString m_str;
public:
explicit StringData(const QString & str) : m_str(str) {}
QString value() const { return m_str; }
};
Run Code Online (Sandbox Code Playgroud)
现在我们可以使用通用元工厂来制作一些具体的元工厂,然后制作我们需要的事件类。我们创建两种携带数据的独特事件类型。这些事件类具有独特的staticType()
s。
// A string event metafactory
template <typename Derived> class StringEventMF : public EventMF<Derived, StringData> {};
class Update : public EventMF<Update, StringData> {}; // using generic metafactory
class Clear : public StringEventMF<Clear> {}; // using specific metafactory
#if 0
// This should fail at compile time as such derivation would produce classes with
// duplicate event types. That's what the Final class was for in the matafactory.
class Error : public Update::Event { Error() : Update::Event("") {} };
#endif
int main(int, char**)
{
// Test that it works as expected.
Update::Event update("update");
Clear::Event clear("clear");
Q_ASSERT(Update::Event::staticType() != Clear::Event::staticType());
Q_ASSERT(Update::Event::staticType() == Update::Event::cast(&update)->staticType());
qDebug() << Update::Event::cast(&update)->value();
Q_ASSERT(Update::Event::cast(&clear) == 0);
qDebug() << Clear::Event::cast(&clear)->value();
Q_ASSERT(Clear::Event::cast(&update) == 0);
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
601 次 |
最近记录: |