【问题标题】:Is there a smart way to implement an observer pattern completely static (at compile time)有没有一种聪明的方法来实现一个完全静态的观察者模式(在编译时)
【发布时间】:2019-10-23 17:00:42
【问题描述】:

编辑:

  • 我想实现一个主题 - 观察者模式,这样我就可以从任何地方调用 notify<SUBJECT>() 并且调用许多 update<SUBJECT>() 函数
  • 主题是结构类型。可以有几个这样的主题
  • 我希望能够将观察者更新函数放在代码中的任何位置(在不同的编译单元中)
  • notify 函数需要知道在编译时要调用哪些更新函数
  • 如果通知功能可以自动连接到更新功能就好了

我正在尝试实现一种观察者模式,即通知机制。我目前使用一种运行时实现,它允许将自身附加到向量。但是,所有“附加”调用都是在启动期间进行的,因此也可以在编译时完成(我希望完成)。

  • 我的第一个想法是没有 int 模板参数,这会导致链接器重新定义错误。如果您可以告诉链接器保留所有它们并调用它们,那就太好了,但我很确定这是做不到的。
  • 现在我使用弱链接和一个 int 模板参数来使方法独一无二。然而,这并不像我想要的那样舒服。必须记住整个代码库中的所有数字。至少,当我使用该数字两次时,我会遇到严重错误。
  • 我对模板元编程不是很深入,但我认为当涉及到不同的编译单元时它会变得复杂(但我也可以切换到单个编译单元)
  • 我想避免使用 boost,但 C++17 完全没问题。

这是常见的标题:

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

我需要摆脱不同编译单元中的数字迭代,但仍然可以有多个相同类型的更新函数的实现。所有在编译时“连接”的都将在运行时调用。

【问题讨论】:

  • 可能不可能。在我看来,您基本上想在每个编译单元的基础上更改notify 的行为,而不更改notify。我的理解是,这只能通过运行时机制来完成。我会等着看专家对此有什么看法。
  • 请重新表述您的问题,因为很难理解您到底想要什么。 C++ 还没有任何迭代所有定义的符号机制,因此如果你想实现 get-all-defined-update-functions-and-call-them-at-compile-time,那么它不是可能的。您需要创建某种模板列表并手动调用它的成员。
  • 如果你专注于你想要完成的事情,而不是专注于你不喜欢的方法,也许你的问题会更容易理解?一个很好的问题/需求陈述,而不是跳入失败的解决方案?
  • 感谢您的反馈,我会尽量说清楚。

标签: c++ templates metaprogramming observer-pattern compile-time


【解决方案1】:

我不确定我是否完全理解您的问题,但如果您对具有相同类型的观察者的限制不是硬性限制,您可以执行一些元编程魔术来迭代类型列表并调用静态 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

【讨论】:

  • 谢谢你的回答,看来你太没理解我的问题了。看起来很有希望,我会尝试把它整合起来,稍后再回来。
【解决方案2】:

可能是这样的:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-28
    • 2015-04-07
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    • 2013-12-03
    • 2021-03-02
    • 1970-01-01
    相关资源
    最近更新 更多