В CRTP я хочу добавить конструктор в производный класс, чисто - без использования макросов и без его написания. Это кажется невозможным, поэтому я придумал некоторые обходные пути.
Во-первых, существует базовый класс событий (QEvent), который должен иметь уникальный тег типа integer для каждого производного класса (см. обоснование). Вы получаете его, вызывая функцию регистрации. Слишком просто создать обертку 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) {}
};
Это, очевидно, старое реальное быстро, даже при перестройке конструктора С++ 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
являются метафакторами: они генерируют для нас желаемый класс событий. Вывод из метафабрики отходит от конкретного типа класса. Тип metafactory дает уникальный дискриминатор типа, необходимый для создания уникальных типов конкретных классов.
Вопросы:
-
Есть ли какой-нибудь "более чистый" или "более желательный" способ сделать это? В идеале следующий нерабочий псевдокод был бы моим идеальным способом сделать это - с нулевым повторением:
class UpdateEvent : public StringEvent <magic>;
Имя производного класса появляется только один раз, а имя базовой концепции
StringEvent
появляется только один раз. CRTP требует, чтобы имя класса появлялось дважды - до сих пор я считаю это приемлемым, но мое метапрограммирование-fu в лохмотьях. Опять же, я хочу использовать препроцессорное решение, в противном случае это было бы нелегко. -
Является ли метабактерическое имя моим оригинальным изобретением (ха-ха), или это просто мой google-Fu, которому не хватает? Эта метафорическая картина кажется довольно гибкой. Легко составлять метафакты путем множественного вывода. Скажем, вам нужен
Update::Event
, сделанный одним factory иUpdate::Foo
, сделанным другим.
Этот вопрос мотивирован этим ответом. Примечание: в реальном коде я бы использовал QString
, но я стараюсь сохранить его как можно более общим.