Is there anything better than a metafactory to work around constructor injection into derived classes in CRTP?

前端 未结 2 545
南笙
南笙 2021-01-02 03:08

In the CRTP, I want to inject the constructor into the derived class, cleanly - without use of macros and without writing it out. It seems it\'s impossible, so I\'ve come up

相关标签:
2条回答
  • 2021-01-02 03:12

    Yochai Timmer has come up with an alternative way of approaching the problem. Instead of having to forward the constructor from the data carrier class, he exposes a factory method that produces pseudo-derived classes. As it invokes undefined behavior, I'm not particularly keen on it.

    Expanding a bit on the original metafactory concept, it's possible to make generic metafactory that can be used to make unique event types that wrap "any" data-carrying class.

    The approach for C++11 uses constructor forwarding so that plain non-template data carrier classes can be used. The approach for C++98 requires a templated data carrier class and, internally, a bit more gymnastics, but it works as well.

    The event classes can't be further derived from. This is necessary since the derived classes would all share the value of staticType, and that can't be allowed, as DyP duly noted in the comments.

    To test the code, you need the event wrapper, the metafactory and data carrier selected for your variant of C++, and the test/usage part.

    The Event Wrapper (Common Code)

    In either case, our basic event wrapper CRTP class that generates a unique static type value for the event is:

    // 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; }
    };
    

    Note that it also provides a cast-to-derived method. You'd use it in an event handler, given a pointer to a base event class:

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

    The C++98 Metafactory

    The Carrier is a parametrized data carrier class, such as StringData below.

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

    The EventFwd class is needed so that we have something sane to pass to the EventWrapper template as the derived class, so that the cast() static method will work. The FinalWrapper is there since in pre-C++11 we can't friend typecasts.

    Now for the parametrized data carrier. It'd be the same as for the C++11 variant below except for needing to have a parametrized base class.

    // 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; }
    };
    

    The C++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 &) {}
        };
    };
    

    The gymanstics with forward-declaration of the Final class are there since forward-declaring the Event class is more typing.

    The data carrier is as simple as it gets:

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

    Usage & Tests (Common Code)

    And now we can use the generic metafactory to make some concrete metafactories, and then to make the event classes we need. We create two unique event types that carry the data. Those event classes have unique 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);
    }
    
    0 讨论(0)
  • 2021-01-02 03:16

    I think what you're looking for might be just using placement new to instantiate the base class.
    The derived class won't be constructable because unless they will create a matching constructor.
    But, they don't have to be constructable, you could use them anyway. (It could still be destructable).

    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> {}
    

    Then let the CRTP base class construct the derived class like this:

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

    If anyone ever wants to initialize the derived class, they should either make a matching constructor that calls the base class constructor.
    OR, just make an .init() method that initiates its members, and will be called after the instance is created.

    OR, we can think of something else, this is just an idea of a concept.

    0 讨论(0)
提交回复
热议问题