Подтвердить что ты не робот

Есть ли что-то лучше метафайла для работы над инъекцией конструктора в производные классы в CRTP?

В 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, но я стараюсь сохранить его как можно более общим.

4b9b3361

Ответ 1

Йохаи Тиммер придумал альтернативный способ приблизиться к проблеме. Вместо того, чтобы пересылать конструктор из класса несущей данных, он предоставляет метод factory, который создает псевдопроизводные классы. Поскольку он вызывает поведение undefined, я не особо заинтересован в этом.

Развернув бит в оригинальной концепции метафайлов, можно создать общий метафайл, который можно использовать для создания уникальных типов событий, которые обертывают "любой" класс переноса данных.

В подходе для С++ 11 используется переадресация конструктора, чтобы можно было использовать простые классы шаблонов несущих шаблонов. Подход для С++ 98 требует шаблонного класса несущих данных и, внутренне, немного больше гимнастики, но он также работает.

Классы событий не могут быть получены из. Это необходимо, так как производные классы будут разделять значение staticType, и это не может быть разрешено, поскольку DyP должным образом отмечен в комментариях.

Чтобы протестировать код, вам понадобится обертка событий, метафайла и носитель данных, выбранный для вашего варианта С++, и часть тестирования/использования.

Обертка событий (общий код)

В любом случае наш базовый класс 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; }
};

Обратите внимание, что он также предоставляет метод отбрасывания к производным. Вы должны использовать его в обработчике событий, указав указатель на базовый класс событий:

void MyClass::customEvent(QEvent* event) {
   if (MyEvent::is(event)) {
      auto myEvent = MyEvent::cast(event);
      // use myEvent to access data carrying members etc)
   }
}

С++ 98 Metafactory

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 cast()
    // will cast to a covariant return type - the derived class. That OK.
    typedef Carrier<FinalWrapper> Event;
private:
    class EventFwd : public Event {};
    class Final {
        friend class FinalWrapper;
        friend class Carrier<FinalWrapper>;
    private:
        Final() {}
        Final(const Final &) {}
    };
};

Класс EventFwd необходим, чтобы у нас было что-то разумное, чтобы перейти к шаблону EventWrapper в качестве производного класса, так что статический метод cast() будет работать. FinalWrapper существует, так как в pre-С++ 11 мы не можем сопоставлять типы типов.

Теперь для параметризованного носителя данных. Он будет таким же, как для варианта С++ 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; }
};

С++ 11 MetaFactory

// 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 &) {}
    };
};

В упражнении с форвардной декларацией класса Final есть так: forward-объявление класса Event более типизировано.

Носитель данных прост, как он получает:

// A string carrier
class StringData {
    QString m_str;
public:
    explicit StringData(const QString & str) : m_str(str) {}
    QString value() const { return m_str; }
};

Использование и тесты (общий код)

И теперь мы можем использовать общий метафайтор, чтобы сделать некоторые конкретные метафакты, а затем сделать классы событий, которые нам нужны. Мы создаем два уникальных типа событий, которые несут данные. Эти классы событий имеют уникальный 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 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);
}

Ответ 2

Я думаю, что вы можете просто использовать размещение нового, чтобы создать базовый класс. < ш > Производный класс не будет конструктивным, потому что если они не создадут соответствующий конструктор.
Но они не должны быть конструктивными, вы можете использовать их в любом случае. (Он все еще может быть разрушаемым).

template <class T>
class Base
{
protected: Base(int blah) { }

public: static T* CreateInstance(int data) { 
            T* newOjectBlock =  reinterpret_cast<T*>(::operator new(sizeof(T))); // allocate enough memory for the derived class
            Base* newBasePlace = (Base*)(newOjectBlock); //point to the part that is reseved for the base class
            newBasePlace=  new ((char*)newBasePlace) Base(data); //call the placement new constrcutor for the base class
            return newOjectBlock;
        }
};

class Derived : public Base<Derived> {}

Затем пусть базовый класс CRTP построит производный класс следующим образом:

Derived* blah =  Derived::CreateInstance(666);

Если кто-либо хочет инициализировать производный класс, он должен либо создать соответствующий конструктор, который вызывает конструктор базового класса.
ИЛИ, просто создайте метод .init(), который инициирует его члены и вызывается после создания экземпляра.

ИЛИ, мы можем думать о чем-то другом, это всего лишь идея концепции.