有没有一种聪明的方法来实现完全静态的观察者模式(在编译时)
Is there a smart way to implement an observer pattern completely static (at compile time)
编辑:
- 我想实现一个主题 - 观察者模式,这样我就可以从任何地方调用
notify<SUBJECT>()
并且许多 update<SUBJECT>()
函数被调用
- 主题是结构类型。可以有几个这样的科目
- 我希望能够在代码中的任何地方(在不同的编译单元中)放置一个观察者更新函数
- 通知函数需要知道在compile-time
调用哪些更新函数
- 如果通知功能可以自动连接到更新功能就好了
我正在尝试实现一种观察者模式,即通知机制。我目前使用 run-time 实现,它允许一个人将自己附加到一个向量上。但是,所有 "attach" 调用都是在启动期间进行的,因此它也可以在 compile-time.
完成(我想完成)
- 我的第一个想法是没有 int 模板参数,这会导致链接器重新定义错误。如果您可以告诉链接器保留所有这些并调用所有这些,那就太好了,但我很确定这是不可能的。
- 现在我使用弱链接和一个 int 模板参数来使方法唯一。但是,这并不像我想要的那样舒服。必须记住整个代码库中的所有数字。至少,当我使用这个数字两次时,我会遇到严重错误。
- 我对模板元编程不是很深入,但我认为当涉及到不同的编译单元时它会变得复杂(但我也可以切换到单个编译单元)
- 我想避免提升,但 C++17 非常好。
这是常见的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
编辑:
- 我想实现一个主题 - 观察者模式,这样我就可以从任何地方调用
notify<SUBJECT>()
并且许多update<SUBJECT>()
函数被调用 - 主题是结构类型。可以有几个这样的科目
- 我希望能够在代码中的任何地方(在不同的编译单元中)放置一个观察者更新函数
- 通知函数需要知道在compile-time 调用哪些更新函数
- 如果通知功能可以自动连接到更新功能就好了
我正在尝试实现一种观察者模式,即通知机制。我目前使用 run-time 实现,它允许一个人将自己附加到一个向量上。但是,所有 "attach" 调用都是在启动期间进行的,因此它也可以在 compile-time.
完成(我想完成)- 我的第一个想法是没有 int 模板参数,这会导致链接器重新定义错误。如果您可以告诉链接器保留所有这些并调用所有这些,那就太好了,但我很确定这是不可能的。
- 现在我使用弱链接和一个 int 模板参数来使方法唯一。但是,这并不像我想要的那样舒服。必须记住整个代码库中的所有数字。至少,当我使用这个数字两次时,我会遇到严重错误。
- 我对模板元编程不是很深入,但我认为当涉及到不同的编译单元时它会变得复杂(但我也可以切换到单个编译单元)
- 我想避免提升,但 C++17 非常好。
这是常见的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);
}
如果您担心在不同的翻译单元上会发生什么,您只需要记住模板是代码生成器:最终,生成的代码将与您手动展开循环一样,并且,如果行得通,这也应该行得通。