有没有一种聪明的方法来实现完全静态的观察者模式(在编译时)

Is there a smart way to implement an observer pattern completely static (at compile time)

编辑:


我正在尝试实现一种观察者模式,即通知机制。我目前使用 run-time 实现,它允许一个人将自己附加到一个向量上。但是,所有 "attach" 调用都是在启动期间进行的,因此它也可以在 compile-time.

完成(我想完成)

这是常见的header:

struct Test {
    int value;
};

namespace Observer
{
    template<typename T, int N>
    void update(decltype(T::value));

    /*
     * This ìs a function dummy which can be overloaded with the
     * real implementation. The linker overrides them and kicks out
     * the unoverridden functions since they do nothing.
     */
    template<typename T, int N>
    void __attribute__((weak)) update(decltype(T::value)) {}
}

template<typename T>
void notify(decltype(T::value) value)
{
    Observer::update<T, 1>(value, value != old);
    Observer::update<T, 2>(value, value != old);
    Observer::update<T, 3>(value, value != old);
}

假设我们有两个编译单元:

template<>
void Observer::update<Test, 1>(decltype(Test::value) value)
{
    std::cout << "update: " << value << std::endl;
}
template<>
void Observer::update<Test, 2>(decltype(Test::value) value)
{
    std::cout << "update: " << value << std::endl;
}

我需要摆脱不同编译单元中的数字迭代,但仍然可以有多个相同类型的更新函数的实现。 compile-time 的所有 "connected" 都将在 run-time 被调用。

也许是这样的:

#include <iostream>
#include <array>
#include <functional>

template<typename Container, typename T>
void notify( Container& observers, T value )
{
   for ( auto& observer : observers )
      observer( value );
}

// updateX can be defined in another unit but exposed in a header
void update1( int i )
{
   std::cout << "observer 1:" << i << std::endl;
}

void update2( int i )
{
   std::cout << "observer 2:" << i << std::endl;
}

const std::array<std::function<void(int)>, 2> observers {
   [](int i) { update1( i ); },
   [](int i) { update2( i ); }
};

int main()
{
   notify( observers, 3 );
   return 0;
}

优化器可能会展开和内联对观察者的调用列表。

我不确定我是否完全理解你的问题,但如果你对具有相同类型的观察者的限制不是硬性限制,你可以做一些元编程魔术来迭代类型列表并调用静态 update() 在他们每个人身上,没有手动展开。

这是一个例子:

#include <iostream>
#include <tuple>
#include <utility>

// A list of types
template<typename... Ts> class observers{
    template<std::size_t I, typename...>
    struct at_impl{};

    template<std::size_t I, typename T, typename... Others>
    struct at_impl<I, T, Others...> {
        using type = typename at_impl<I-1, Others...>::type;
    };

    template<typename T, typename... Others>
    struct at_impl<0, T, Others...> {
        using type = T;
    };

public:

    // A way of getting the nth-type listed
    template<std::size_t I>
    using at = typename at_impl<I, Ts...>::type;

    constexpr inline static std::size_t size = sizeof...(Ts);
};

// Our notification function will iterate a list of observers, and call update() on each one.

template<typename ObserverList, std::size_t I = 0>
std::enable_if_t< ObserverList::size <= I, void>
notify_impl(int v) {}

template<typename ObserverList, std::size_t I = 0>
std::enable_if_t< I < ObserverList::size, void> 
notify_impl(int v) {
    using T = typename ObserverList::template at<I>;
    T::update(v);
    notify_impl<ObserverList, I+1>(v);
}

template<typename ObserverList>
void notify(int v) {
    notify_impl<ObserverList, 0>(v);
}

// Let's define some observers...

struct FirstObserver {
    static void update(int v){std::cout << "first observer notified with value = " << v << '\n';}
};

struct SecondObserver {
    static void update(int v){std::cout << "second observer notified with value = " << v << '\n';}
};

struct ThirdObserver {
    static void update(int v){std::cout << "third observer notified with value = " << v << '\n';}
};

// ... and put those observers on a list.
using ObserverList = observers<FirstObserver, SecondObserver, ThirdObserver>;

int main() {
    notify<ObserverList>(0);
}

输出:

first observer notified with value = 0
second observer notified with value = 0
third observer notified with value = 0

See it live!


然而,如果你只是想迭代地调用Observer::update,你可以只实现notify如下:

template<typename T, std::size_t I, std::size_t MAX>
std::enable_if_t< MAX <= I, void>
notify_impl(decltype(T::value)) {}

template<typename T, std::size_t I, std::size_t MAX>
std::enable_if_t< I < MAX, void> 
notify_impl(decltype(T::value) v) {
    Observer::update<T, I>(v);
    notify_impl<T, I+1, MAX>(v);
}

template<typename T>
void notify(decltype(T::value) v) {
    // Assuming we have 3 observers
    notify_impl<T, 0, 3>(v);
}

如果您担心在不同的翻译单元上会发生什么,您只需要记住模板是代码生成器:最终,生成的代码将与您手动展开循环一样,并且,如果行得通,这也应该行得通。

Live example