第四章:让数据源作为成员变量

在本章中,我们将实现 constVaractionfield

我们的目标是让如下代码成功编译并通过测试:

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);
// b.value(4.14); // compile error;
}
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) {
// 创建响应式节点(AlwaysTrigger策略)
auto ptr = std::make_shared<ReactImpl<AlwaysTrigger, DirectCloseStrategy, std::decay_t<T>>>(
std::forward<T>(t)
);

// 注册到全局字段图(关键点1)
FieldGraph::getInstance().addObj(m_id, ptr->getShared());

return React{ptr}; // 返回React包装
}

virtual ~FieldBase() {
// 对象销毁时自动清理(关键点2)
FieldGraph::getInstance().deleteObj(m_id);
}

protected:
// 唯一标识生成器(模板化实现)
UniqueID<FieldBase> m_id;
};

实现要点

  1. 每个派生类实例自动获得唯一ID
  2. 字段构造时自动注册到所属对象
  3. 析构时自动解除注册

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()) {
// 双向观察建立(关键点3)
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;
};

观察关系建立流程

  1. 对象创建var时触发setField调用
  2. 遍历对象的所有注册字段
  3. 建立对象与字段的双向观察:
    • 字段变更 → 通知所属对象
    • 对象变更 → 通知所有字段

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

// 编译期类型检查(关键点4)
if constexpr (std::is_base_of_v<FieldBase, RawT>) {
auto ptr = std::make_shared<ReactImpl<AlwaysTrigger, DirectCloseStrategy, RawT>>(
std::forward<T>(t)
);

// 延迟建立观察关系(关键点5)
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而非反射的原因:
    1. 编译期确定性:避免运行时类型检查开销
    2. 更好的类型安全:强制使用FieldBase派生类
    3. 简化实现:无需复杂反射基础设施

三、生命周期管理策略

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) {
    // 针对随机访问迭代器的实现
    }

    // 入口函数:通过 traits 提取标签并分发
    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
// 在 React 对象中获取引用时:
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; // a 的类型是 int(引用被移除)
decltype(auto) b = rx; // b 的类型是 int&(引用保留)
1
2
3
int&& foo();
auto x = foo(); // x 是 int(右值引用被移除)
decltype(auto) y = foo(); // y 是 int&&(保留右值引用)

三、主要应用场景

  1. 函数返回值类型推导
1
2
3
4
5
6
int x = 42;
int& getX() { return x; }

decltype(auto) getValue() {
return getX(); // 保留返回引用
}

如果用 auto,返回值会被拷贝;用 decltype(auto),可以保留引用或右值引用的属性。

  1. 模板中精确转发和返回类型
1
2
3
4
template<typename T>
decltype(auto) forwardValue(T&& val) {
return std::forward<T>(val);
}