11

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。和是UpdateClear工厂:它们为我们生成所需的事件类。从元工厂派生回避了从具体类类型派生。元工厂类型提供了创建唯一具体类类型所需的唯一类型鉴别器。

问题是:

  1. 有没有“更清洁”或“更理想”的方法?理想情况下,以下非工作伪代码将是我理想的实现方式 - 零重复:

    class UpdateEvent : public StringEvent <magic>;
    

    派生类的名称只出现一次,基本概念的名称也StringEvent只出现一次。CRTP 要求类名出现两次 - 到目前为止,我认为这是可以接受的,但我的元编程功能已经破烂。再一次,我想要一个无预处理器的解决方案,否则它会很容易。

  2. Metafactory这个名字是我最初的发明(哈哈),还是仅仅是我缺少的 google-Fu?这种元工厂模式似乎非常灵活。通过多重推导很容易组成元工厂。假设您想要一个Update::Event由一家工厂Update::Foo制造,并由另一家工厂制造。

这个问题是由这个答案引起的。注意:在实际代码中我将使用QString,但我试图使其尽可能通用。

4

2 回答 2

3

我认为您正在寻找的可能只是使用placement new来实例化基类。
派生类将不可构造,因为除非它们创建匹配的构造函数。
但是,它们不必是可构造的,无论如何你都可以使用它们。(它仍然可以被破坏)。

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() 方法来启动其成员,并将在创建实例后调用。

或者,我们可以想到别的东西,这只是一个概念的想法。

于 2013-10-04T18:54:13.430 回答
3

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

请注意,它还提供了转换为派生的方法。您将在事件处理程序中使用它,给定一个指向基事件类的指针:

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

C++98 元工厂

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

需要该类EventFwd,以便我们可以将一些理智的东西EventWrapper作为派生类传递给模板,以便cast()静态方法可以工作。因为在 C++11 之前的FinalWrapper版本中,我们无法使用类型转换。

现在是参数化的数据载体。除了需要具有参数化的基类之外,它与下面的 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; }
};

C++11 元工厂

// 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 类的前向声明的体操是存在的,因为前向声明 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'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);
}
于 2013-10-04T20:10:28.957 回答