第四章:让数据源作为成员变量 在本章中,我们将实现 constVar
、action
和 field
。
我们的目标是让如下代码成功编译并通过测试:
1 2 3 4 5 6 7 8 9 10 TEST (ReactionTest, TestConst) { auto a = reaction::var (1 ); auto b = reaction::constVar (3.14 ); auto ds = reaction::calc ([](int aa, double bb) { return aa + bb; }, a, b); ASSERT_FLOAT_EQ (ds.get (), 4.14 ); a.value (2 ); ASSERT_FLOAT_EQ (ds.get (), 5.14 ); }
1 2 3 4 5 6 7 8 9 TEST (ReactionTest, TestAction) { auto a = reaction::var (1 ); auto b = reaction::var (3.14 ); auto at = reaction::action ([](int aa, double bb) { std::cout << "a = " << aa << '\t' << "b = " << bb << '\t' ; }, a, b); a.value (2 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Person : public reaction::FieldBase {public : Person (std::string name, int age, bool male) : m_name (field (name)), m_age (field (age)), m_male (male) { } std::string getName () const { return m_name.get (); } void setName (const std::string &name) { *m_name = name; } int getAge () const { return m_age.get (); } void setAge (int age) { *m_age = age; } private : reaction::Field<std::string> m_name; reaction::Field<int > m_age; bool m_male; }; TEST (BasicTest, FieldTest) { Person person{"lummy" , 18 , true }; auto p = reaction::var (person); auto a = reaction::var (1 ); auto ds = reaction::calc ([](int aa, auto pp) { return std::to_string (aa) + pp.getName (); }, a, p); EXPECT_EQ (ds.get (), "1lummy" ); p->setName ("lummy-new" ); EXPECT_EQ (ds.get (), "1lummy-new" ); }
深度实现解析:基于继承的字段管理系统 一、核心架构设计 1.1 类关系图谱 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 classDiagram class FieldBase{ <<abstract>> +uint64_t m_id +field(T&&) React +~FieldBase() } class Person{ +Field<std::string> m_name +Field<int> m_age +bool m_male +getName() string +setName(string) } class FieldGraph{ -unordered_map<uint64_t, unordered_set<NodePtr>> m_fieldMap +addObj() +deleteObj() +setField() } class ReactImpl{ -T m_value +updateObservers() +addObserver() } FieldBase <|-- Person FieldGraph o-- ReactImpl Person *-- ReactImpl
二、关键实现细节 2.1 字段注册机制(FieldBase) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class FieldBase {public : template <typename T> auto field (T &&t) { auto ptr = std::make_shared<ReactImpl<AlwaysTrigger, DirectCloseStrategy, std::decay_t <T>>>( std::forward<T>(t) ); FieldGraph::getInstance ().addObj (m_id, ptr->getShared ()); return React{ptr}; } virtual ~FieldBase () { FieldGraph::getInstance ().deleteObj (m_id); } protected : UniqueID<FieldBase> m_id; };
实现要点 :
每个派生类实例自动获得唯一ID
字段构造时自动注册到所属对象
析构时自动解除注册
2.2 字段关系图(FieldGraph增强实现) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class FieldGraph {public : void setField (uint64_t id, NodePtr objPtr) { std::lock_guard<std::mutex> lock (m_mutex) ; if (auto it = m_fieldMap.find (id); it != m_fieldMap.end ()) { for (auto & node : it->second) { node->addObserver (objPtr); objPtr->addObserver (node); } } } private : std::mutex m_mutex; std::unordered_map<uint64_t , std::unordered_set<NodePtr>> m_fieldMap; };
观察关系建立流程 :
对象创建var时触发setField调用
遍历对象的所有注册字段
建立对象与字段的双向观察:
字段变更 → 通知所属对象
对象变更 → 通知所有字段
2.3 var模板的特化处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 template <typename T>auto var (T&& t) { using RawT = std::decay_t <T>; if constexpr (std::is_base_of_v<FieldBase, RawT>) { auto ptr = std::make_shared<ReactImpl<AlwaysTrigger, DirectCloseStrategy, RawT>>( std::forward<T>(t) ); auto & graph = FieldGraph::getInstance (); graph.setField (ptr->get ().getId (), ptr->getShared ()); return React{ptr}; } else { return React{std::make_shared<ReactImpl<AlwaysTrigger, DirectCloseStrategy, RawT>>( std::forward<T>(t)) }; } }
设计决策分析 :
选择is_base_of_v
而非反射的原因:
编译期确定性 :避免运行时类型检查开销
更好的类型安全 :强制使用FieldBase派生类
简化实现 :无需复杂反射基础设施
三、生命周期管理策略 3.1 对象注册流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 sequenceDiagram participant User participant Person participant FieldGraph participant ReactImpl User->>Person: 创建实例 Person->>FieldBase: 构造(生成m_id) loop 初始化字段 Person->>FieldBase: field("lummy") FieldBase->>ReactImpl: 创建字段实例 FieldBase->>FieldGraph: addObj(m_id, fieldPtr) end User->>var模板: 创建数据源 var模板->>FieldGraph: setField(m_id, objPtr) FieldGraph->>ReactImpl: 建立双向观察
3.2 对象销毁流程 1 2 3 4 5 6 7 8 9 10 11 sequenceDiagram participant User participant Person participant FieldGraph participant ReactImpl User->>Person: 析构实例 Person->>FieldBase: 析构 FieldBase->>FieldGraph: deleteObj(m_id) FieldGraph->>ReactImpl: 移除所有关联字段 ReactImpl->>Observers: 自动解除观察关系
此外,为了在 var
接收到自定义类型时,识别其继承 FieldBase
并自动调用 setField
,建议在 VarImpl
构造中使用 std::is_base_of_v<FieldBase, T>
判断并分发。
模板元编程常用技法:标签分发(Tag Dispatching)
核心思想 :通过定义空结构体(标签)表示类型特性,利用函数重载或模板特化分发到不同实现。
典型场景 :标准库算法根据迭代器类型(如 std::input_iterator_tag
)选择最优实现。
示例 :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct InputTag {};struct RandomAccessTag {};template <typename Iter>void algorithm (Iter begin, Iter end, InputTag) { } template <typename Iter>void algorithm (Iter begin, Iter end, RandomAccessTag) { } template <typename Iter>void algorithm (Iter begin, Iter end) { using Tag = typename std::iterator_traits<Iter>::iterator_category; algorithm (begin, end, Tag{}); }
最后,说明一下 decltype(auto)
的使用场景:
1 2 3 4 decltype (auto ) getRef () const { return this ->getReference (); }
decltype(auto)
是 C++14 引入的一个类型推导工具,用于根据表达式的值类型(value category)精确推导变量或返回值的类型。
一、基本作用 它的作用可以理解为:
decltype(auto)
会完全照搬 表达式的类型,包括是否为引用、是否为 const
。
和 auto
不同,decltype(auto)
不会移除引用或 const
修饰。
二、示例说明 1 2 3 4 5 int x = 10 ;int & rx = x;auto a = rx; decltype (auto ) b = rx;
1 2 3 int && foo () ;auto x = foo (); decltype (auto ) y = foo ();
三、主要应用场景
函数返回值类型推导 :
1 2 3 4 5 6 int x = 42 ;int & getX () { return x; }decltype (auto ) getValue () { return getX (); }
如果用 auto
,返回值会被拷贝;用 decltype(auto)
,可以保留引用或右值引用的属性。
模板中精确转发和返回类型 :
1 2 3 4 template <typename T>decltype (auto ) forwardValue (T&& val) { return std::forward<T>(val); }