使用CRTP和'匿名类型/模板'

时间:2015-10-24 17:11:27

标签: c++ templates c++11 observer-pattern crtp

我正在开发一个解析某个XML文件的库(使用RapidXML)并返回包含该文件数据的我的对象。该XML文件由其他人的应用程序创建。我需要使用观察者模式,因为速度非常关键,例如:

假设一个文件有10.000个标记及其子节点。在一个简单的解析器中,它们将按照它们被找到的顺序添加到std :: vector中。然后,在解析文件之后,我们需要迭代10.000的值,然后用它们做任何我们想做的事情。

通过使用观察者模式,我允许外部观察者(无论哪个类想要观察并获得有关每个获取节点的通知都必须从我的库附带的AbstractObserver继承并实现其功能)成为解析的一部分进程无需在解析的节点上再次迭代X次。但是......有多种节点,例如:tileset,layer,imagelayer等(根据Observer / Subject模式,需要多个onObserved和notify函数用于相应的节点,可能有很多&# 39;重复的代码 - 注意:不使用继承。请参阅下面的“不好的例子”。我可以简单地让节点从某种BaseNode类继承,但我不想在这里使用继承,因为我不想处理指针。相反,我使用枚举来键入节点,这就是我的问题所在。

/* ################## ABSTRACT OBSERVER #################### */
// Implements the observer pattern, using the CRTP pattern
template<class ConcreteObserver>
class AbstractObserver
{
public:
    virtual ~AbstractObserver() { }

    template<class Attribute>
    inline void onObserved(Attribute attribute) {
        // This requires ConcreteObserver to have a method onObservedImpl<Attribute>
        static_cast<const ConcreteObserver*>(this)->onObservedImpl(attribute);
    }
};

/* ################## ABSTRACT SUBJECT #################### */
class AbstractSubject
{
public:
    virtual ~AbstractSubject() { }

    // ???????
    inline void attach(AbstractObserver<??????>* observer) {
        m_observers.push_back(observer);
    }

    // ???????
    inline void detatch(AbstractObserver<??????>* observer) { 
        auto& it = std::find(m_observers.begin(), m_observers.end(), observer);

        // Remove the observer from the list, if it was observing
        if (it != m_observers.end())
            m_observers.erase(it);
    }

protected:
    template<typename Attribute>
    void notify(Attribute& attribute) {
        for (auto* observer : m_observers)
            observer->onObserved(attribute)
    }

private:
    // ???????
    std::vector<AbstractObserver<??????>*> m_observers;
};

/* ################## CONCRETE OBSERVER #################### */
class SomeConcreteObserver : public AbstractObserver<SomeConcreteObserver>
{
public:
    // The CRTP 'virtual' function implementation
    template<class Attribute>
    void onObservedImpl(Attribute attribute)
    {
        // Filter the attribute and use it accordingly
        switch (attribute.type)
        {
            // ....
        }
    }
};

/* ################## CONCRETE SUBJECT #################### */
class Parser : public AbstractSubject
{
public:
    void parse(/* params */)
    {
        Foo f;

        notify<Foo>(f);

        // Later on....

        Bar b;

        notify<Bar>(b);
    }
};

正如我们所看到的,我也使用CRTP,因为我需要模板化虚拟功能,否则无法实现。由于AbstractObserver需要一个类型(因为CRTP),我无法在AbstractSubject类中正确使用它们(见上文)。甚至可以像Java一样使用匿名模板,还是类似的东西?我相信这可以做到这一点。

以下是“坏”的实施情况。我想到的例子,但这是我能想到的最好的情况:

// Remove the CRTP
class AbstractObserver
{
public:
    virtual ~AbstractObserver() { }

    virtual void onNodeA(NodeA n) = 0;
    virtual void onNodeB(NodeB n) = 0;
    virtual void onNodeC(NodeC n) = 0;
    virtual void onNodeD(NodeD n) = 0;
    virtual void onNodeE(NodeE n) = 0;
    // .....
};

class AbstractSubject
{
public:
    // ....

protected:
    void notifyNodeA(NodeA n) { 
        for (auto* observer : m_observers)
                observer->onNodeA(n);
    }
    void notifyNodeB(NodeB n) { 
        for (auto* observer : m_observers)
                observer->NodeB(n);
    }
    void notifyNodeC(NodeC n) { }
    void notifyNodeD(NodeD n) { }
    void notifyNodeE(NodeE n) { }
    // ....

private:
    std::vector<Observer*> m_observers;
};

解决方案必须使用C ++ 11或低于此而不是提升。

1 个答案:

答案 0 :(得分:0)

解决方案#1:相当潮湿但很简单

#include <vector>
#include <algorithm>
#include <iostream>

template<typename TAttribute>
class Observer
{
public:
    virtual void Observe(TAttribute& attribute) = 0;

    virtual ~Observer() = default;
};

template<typename TAttribute>
class OutputtingObserver : public Observer<TAttribute>
{
public:
    void Observe(TAttribute& attribute)
    {
        std::cout << attribute << std::endl;
    }

    ~OutputtingObserver() = default;
};

template<typename TAttribute>
class Subject
{
private:
    std::vector<Observer<TAttribute>*> mutable m_observers;

public:
    void Attach(Observer<TAttribute>& observer) const
    {
        m_observers.push_back(&observer);
    }

    void Detach(Observer<TAttribute>& observer) const
    {
        m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
    }

    void Notify(TAttribute& attribute)
    {
        for (auto observer : m_observers)
            observer->Observe(attribute);
    }
};

class NodeA
{
public:
    friend std::ostream& operator<<(std::ostream& o, const NodeA& node)
    {
        return o << "a";
    }
};

class NodeB
{
public:
    friend std::ostream& operator<<(std::ostream& o, const NodeB& node)
    {
        return o << "b";
    }
};

class Parser
{
private:
    Subject<NodeA> m_subjectA;
    Subject<NodeB> m_subjectB;

public:
    void Parse()
    {
        auto a = NodeA();
        auto b = NodeB();

        m_subjectA.Notify(a);
        m_subjectB.Notify(b);
    }

    void Attach(Observer<NodeA>& observer)
    {
        m_subjectA.Attach(observer);
    }

    void Attach(Observer<NodeB>& observer)
    {
        m_subjectB.Attach(observer);
    }
};

int main()
{
    auto observerA = OutputtingObserver<NodeA>();
    auto observerB = OutputtingObserver<NodeB>();

    auto parser = Parser();

    parser.Attach(observerA);
    parser.Attach(observerB);
    parser.Attach(observerA);

    parser.Parse();

    return 1;
}

您需要在此处使用合成,并为每种类型的节点设置主题。但是,这是经过编译时验证的,所以我更喜欢第二个版本。

解决方案#2:动态且更接近您想要的

#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>

class ObserverBase
{
public:
    virtual ~ObserverBase() = default;
};

template<typename TAttribute>
class Observer : public ObserverBase
{
public:
    virtual void Observe(TAttribute& attribute) = 0;
};

template<typename TAttribute>
class OutputtingObserver : public Observer<TAttribute>
{
public:
    void Observe(TAttribute& attribute)
    {
        std::cout << attribute << std::endl;
    }

    ~OutputtingObserver() = default;
};

template<typename TKey>
class Subject
{
private:
    using ObserverList = std::vector<ObserverBase*>;
    using ObserverMap = std::unordered_map<TKey, ObserverList>;

    ObserverMap mutable m_observers;

public:
    void Attach(TKey key, ObserverBase& observer) const
    {
        auto itr = m_observers.find(key);
        if (itr == m_observers.end())
        {
            m_observers.emplace(std::make_pair(key, ObserverList { &observer }));
            return;
        }

        itr->second.push_back(&observer);
    }

    void Detach(ObserverBase& observer) const
    {
        m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
    }

    template<TKey key, typename TAttribute>
    void Notify(TAttribute& attribute)
    {
        auto itr = m_observers.find(key);
        if (itr == m_observers.end())
            return;

        for (auto observer : itr->second)
            dynamic_cast<Observer<TAttribute>*>(observer)->Observe(attribute);
    }
};

enum class NodeType
{
    TypeA,
    TypeB
};

class NodeA
{
public:
    friend std::ostream& operator<<(std::ostream& o, const NodeA& node)
    {
        return o << "a";
    }
};

class NodeB
{
public:
    friend std::ostream& operator<<(std::ostream& o, const NodeB& node)
    {
        return o << "b";
    }
};

class Parser
{
private:
    Subject<NodeType> m_subject;

public:
    void Parse()
    {
        auto a = NodeA();
        auto b = NodeB();

        m_subject.Notify<NodeType::TypeA, NodeA>(a);
        m_subject.Notify<NodeType::TypeB, NodeB>(b);
    }

    void Attach(Observer<NodeA>& observer)
    {
        m_subject.Attach(NodeType::TypeA, observer);
    }

    void Attach(Observer<NodeB>& observer)
    {
        m_subject.Attach(NodeType::TypeB, observer);
    }
};

int main()
{
    auto observerA = OutputtingObserver<NodeA>();
    auto observerB = OutputtingObserver<NodeB>();

    auto parser = Parser();

    parser.Attach(observerA);
    parser.Attach(observerB);
    parser.Attach(observerA);

    parser.Parse();

    return 1;
}

这更接近您的版本。相当不安全,速度慢但输入略少。

摘要

输出a\na\nb和两者都是缝合在一起的东西,只是作为概念的最小证明,而不是你应该遵循的东西(特别是使用unordered_map感觉非常讨厌)。

这不是你想要的,但我想你可以从那里拿走它......

我有强烈的感觉,有更好的解决方案,所以随时尝试。

修改

解决方案#3:完全动态

#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
#include <typeinfo>
#include <typeindex>

template<typename TAttribute>
class Observer
{
public:
    virtual void Observe(TAttribute& attribute) = 0;

    virtual ~Observer() = default;
};

class Subject
{
private:
    using ObserverList = std::vector<void*>;
    using ObserverMap = std::unordered_map<std::type_index, ObserverList>;

    ObserverMap mutable m_observers;

public:
    template<typename TAttribute>
    void Attach(Observer<TAttribute>& observer) const
    {
        auto index = std::type_index(typeid(Observer<TAttribute>));
        auto itr = m_observers.find(index);
        if (itr == m_observers.end())
        {
            m_observers.emplace(std::make_pair(index, ObserverList { &observer }));
            return;
        }

        itr->second.push_back(&observer);
    }

    template<typename TAttribute>
    void Detach(Observer<TAttribute>& observer) const
    {
        m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
    }

    template<typename TAttribute>
    void Notify(TAttribute& attribute)
    {
        auto itr = m_observers.find(std::type_index(typeid(Observer<TAttribute>)));
        if (itr == m_observers.end())
            return;

        for (auto observer : itr->second)
            static_cast<Observer<TAttribute>*>(observer)->Observe(attribute);
    }
};

这基本上是移植的C#版Dictionary<Type, Object>,它使用rtti,所以你可能会被C ++强硬派唾弃...