实现自动生成变量的Setter&Getter(Qt版)

实现自动生成变量的Setter&Getter(Qt版)

这玩意儿是啥?

Qt提供了非常简易方便的信号槽机制,基于Qt的信号槽,我们能够快速实现一个观察者模式。在观察者模式中,被观察的对象(假设为Model)内有相应的属性数据,观察者们(假设为Controller_(1-n))通过注册槽函数监视着被观察对象的变化,当被观察的对象发生变化时,会扔出信号,观察者们在槽函数中进行响应。

在这个过程中,被观察的对象需要有相应的属性数据,并提供相应的Set接口来改变属性数据,属性数据发生改变后,会自动扔出属性被改变的信号,同时被观察的对象还可以提供Get接口来直接获取属性数据。

我们打算实现的,就是简化代码,用少量的代码来生成:属性数据、Set&Get接口函数、调用Set接口设置属性数据后自动扔出信号。

这玩意儿解决什么问题?

解决的问题

对于被观察的对象,它们内部的所有属性数据都有共同的地方:需要Set&Get函数、需要属性被改变时扔出去的信号、在调用Set函数设置属性数据后扔出信号。如果我们纯手写这些东西,也能达到最终的目的,但是我们需要手写和维护许多的Set&Get函数以及信号,当属性数据变得很多的时候,手写和维护的工作量都会变得很庞大,一不小心就容易出错,而且会有很多重复的代码。作为一名合格的超懒程序员,希望能把这些东西做成自动生成的,将重复代码降到最低,提升代码整体的可维护性。

防止回环

在进入实现之前,我们得先解决一个小问题。

看个简单的图,Controller_(2-n)绑定了Model中的一个信号,在一个属性数据被设置后会扔出这个信号,Controller_1通过Model提供的Set接口设置了这个属性数据,Controller_(2-n)响应Model中这个属性数据的改变。

1
2
3
4
5
graph LR
Controller_1--Set-->Model
Model--Signal-->Controller_2
Model--Signal-->Controller_3
Model--Signal-->Controller_n

如果Controller_1也绑定了这个信号,这个由Controller_1调用Set接口改变Model中的属性数据而引发的信号,Controller_1自己也会收到,但是Controller_1很可能只希望在别的Controller对Model的这个属性数据进行操作时才响应,但是此时Controller_1无法分辨出这个属性数据是由它自己改变的还是由其他Controller改变的。 (一个例子是,Controller_1对接了相应的窗口,窗口中有一个输入框,允许用户改变这个属性数据,其他Controller也对接了其他的窗口,也有同样的输入框能改变这个属性数据,当用户在Controller_1相关的窗口的输入框内改变了这个属性数据后,Controller_1会调用Set函数改变Model中的属性数据,其他窗口接收到信号后更新自己输入框内的数据保持同步显示,此时Controller_1也会收到这个信号,但是用户的操作是在Controller_1相关的窗口上的,Controller_1其实可以不用去更新窗口的数据,由于没法判断是由谁触发的改变,Controller_1只能更新窗口的数据。)

1
2
3
4
5
6
graph LR
Controller_1--Set-->Model
Model--Signal-->Controller_1
Model--Signal-->Controller_2
Model--Signal-->Controller_3
Model--Signal-->Controller_n

我们可以在Set函数中给一个调用源,来标识触发改变的源对象,我们可以直接使用调用Set函数的Controller的类实例地址来作为调用源的标识符,因为地址是唯一的。 (这样上面例子中的Controller_1就能在槽函数中通过标识符确定信号是由自己调用Set函数而触发的,Controller_1可以不用更新窗口中的数据了。)

这玩意儿咋实现?

来个特例的实现

假设我们的Model中有一个颜色属性数据Color,有RGBA共4个int型的分量,那么我们针对Color,在Model中就要有如下的代码。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Model : public QObject {
...
signals:
void ColorChanged(uintptr_t changer, int propColorR, int propColorG, int propColorB, int propColorA);
public:
void SetPropsColor(uintptr_t changer, const std::tuple<int, int, int, int>& color)
{
propColorR = std::get<0>(color);
propColorG = std::get<1>(color);
propColorB = std::get<2>(color);
propColorA = std::get<3>(color);
emit ColorChanged(changer, propColorR, propColorG, propColorB, propColorA);
}
std::tuple<int, int, int, int> GetPropsColor() const
{
return{ propColorR, propColorG, propColorB, propColorA };
}
void SetColorR(uintptr_t changer, int propColorR)
{
this->propColorR = propColorR;
emit ColorChanged(changer, propColorR, propColorG, propColorB, propColorA);
}
int GetColorR() const
{
return propColorR;
}
void SetColorG(uintptr_t changer, int propColorG)
{
this->propColorG = propColorG;
emit ColorChanged(changer, propColorR, propColorG, propColorB, propColorA);
}
int GetColorG() const
{
return propColorG;
}
void SetColorB(uintptr_t changer, int propColorB)
{
this->propColorB = propColorB;
emit ColorChanged(changer, propColorR, propColorG, propColorB, propColorA);
}
int GetColorB() const
{
return propColorB;
}
void SetColorA(uintptr_t changer, int propColorA)
{
this->propColorA = propColorA;
emit ColorChanged(changer, propColorR, propColorG, propColorB, propColorA);
}
int GetColorA() const
{
return propColorA;
}
private:
int propColorR;
int propColorG;
int propColorB;
int propColorA;
};

由特例到通用化

我们期望上面的代码能够通用化,这样才能自动去生成。

在这条路子上,C++超级强大的模板也施展不了手脚了(是我太菜T_T),Qt的继承自QObject的类不能再声明一个继承自QObject的内部类[可参考下方的示例代码],没有类没有函数,模板凉了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Model : public QObject {
public:
class Color : public QObject { // 此处会导致编译不通过!!!继承自QObject的类不能嵌套!!!
public:
void SetPropsColor(uintptr_t changer, const std::tuple<int, int, int, int>& color) {...}
std::tuple<int, int, int, int> GetPropsColor() const {...}
void SetColorR(uintptr_t changer, int propColorR) {...}
int GetColorR() const {...}
...
signals: // 只有继承自QObject的类才能扔出信号,我们需要扔出Color属性数据被改变的信号
void ColorChanged(uintptr_t changer, int propColorR, int propColorG, int propColorB, int propColorA);
private:
int propColorR;
int propColorG;
int propColorB;
int propColorA;
}
...
signals:
...
private:
...
}

没关系,我们还有一个终极利器宏定义。当下的许多IDE也都支持了宏展开后的函数的提示和联想功能。

在特例中,我们能发现除了属性数据名称属性数据值名称属性数据值类型是不同的外,其他的大部分对于不同的属性数据其实是一致的,那么这些一致的部分就是我们需要自动生成的部分。定义宏名及宏参数列表,宏参数列表依次就是属性数据名称、属性数据值依次的名称,属性数据值依次的类型。

1
2
3
4
#define PROPERTY_GENERATOR_SETGET_4(n, n1, n2, n3, n4, t1, t2, t3, t4) \
...
#define PROPERTY_GENERATOR_SIGNAL_4(n, n1, n2, n3, n4, t1, t2, t3, t4) \
...

这里我们把生成过程拆成了两个宏,一个是SETGET,一个是SIGNAL,这么做的原因是Qt的信号槽机制依赖于Qt自己的moc预编译器,moc预编译器在正常的C++编译器编译之前会预编译我们写的代码,moc预编译器只有在源码中找到signals关键字才处理信号函数,如果我们把signals写在了宏中,moc预编译器的处理早于C++的编译器,此时宏还没有被展开和替换,moc预编译器不会在宏中寻找signals关键字,只会在找到关键字后在处理中遇到宏才进行展开,如果我们不把Set&Get函数与信号分开,都写在宏中,signals关键字只能放在宏内,这就会导致moc预编译器不生成Qt的信号函数,最终导致编译出错。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#define PROPERTY_GENERATOR_SETGET_4(n, n1, n2, n3, n4, t1, t2, t3, t4)           \
public: \
void SetProps##n(uintptr_t changer, const std::tuple<t1, t2, t3, t4>& props) \
{ \
prop##n1 = std::get<0>(props); \
prop##n2 = std::get<1>(props); \
prop##n3 = std::get<2>(props); \
prop##n4 = std::get<3>(props); \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
std::tuple<t1, t2, t3, t4> GetProps##n() const \
{ \
return { prop##n1, prop##n2, prop##n3, prop##n4 }; \
} \
void Set##n1(uintptr_t changer, t1 prop##n1) \
{ \
this->prop##n1 = prop##n1; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t1 Get##n1() const \
{ \
return prop##n1; \
} \
void Set##n2(uintptr_t changer, t2 prop##n2) \
{ \
this->prop##n2 = prop##n2; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t2 Get##n2() const \
{ \
return prop##n2; \
} \
void Set##n3(uintptr_t changer, t3 prop##n3) \
{ \
this->prop##n3 = prop##n3; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t3 Get##n3() const \
{ \
return prop##n3; \
} \
void Set##n4(uintptr_t changer, t4 prop##n4) \
{ \
this->prop##n4 = prop##n4; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t4 Get##n4() const \
{ \
return prop##n4; \
} \
private: \
t1 prop##n1; \
t2 prop##n2; \
t3 prop##n3; \
t4 prop##n4;

#define PROPERTY_GENERATOR_SIGNAL_4(n, n1, n2, n3, n4, t1, t2, t3, t4) \
void n##Changed(uintptr_t changer, t1 prop##n1, t2 prop##n2, t3 prop##n3, t4 prop##n4);

写好生成宏后,我们的属性数据就能十分快速地用宏来生成相应的Set&Get函数代码、信号及属性数据值的声明了。我们只需要在Model类声明中写上下面简单的几行,就能在Model类中声明propColorR、propColorB、propColorG、propColorA变量,生成SetPropsColor、SetColorR、SetColorG、SetColorB、SetColorA、GetPropsColor、GetColorR、GetColorG、GetColorB、GetColorA函数,生成ColorChanged信号,并在Set函数中扔出ColorChanged信号。舒服~

1
2
3
4
5
6
7
8
9
class Model : public QObject {
...
signals:
...
PROPERTY_GENERATOR_SIGNAL_4(Color, ColorR, ColorG, ColorB, ColorA, int, int, int, int)
...
PROPERTY_GENERATOR_SETGET_4(Color, ColorR, ColorG, ColorB, ColorA, int, int, int, int)
...
};

最后,我们再提供一个生成changer的宏方便生成changer。

1
#define PROPERTY_GENERATOR_MAKE_CHANGER(c) reinterpret_cast<uintptr_t>(c)

前面也说到调用源的标识符,changer就是这个标识符,用来标识触发改变的源对象,它通常是Controller的类实例地址,那么我们在Controller内调用Set的地方,直接用宏包装this指针来生成这个标识符吧。

1
model.SetColorR(PROPERTY_GENERATOR_MAKE_CHANGER(this), 255);

把宏写短一点儿

这个宏写得简直又臭又长,又臭又长的宏确实能降低撞宏的概率,但是在实际项目中打这么长的宏手也很累啊。赶紧简化简化:

1
2
3
4
5
6
7
8
#ifndef PROPERTY_GENERATOR_FORCE_FULL_MACRO
...
#define PG_SETGET_4 PROPERTY_GENERATOR_SETGET_4
...
#define PG_SIGNAL_4 PROPERTY_GENERATOR_SIGNAL_4
...
#define PG_MAKE_CHANGER PROPERTY_GENERATOR_MAKE_CHANGER
#endif

嘿,这样只要没有定义PROPERTY_GENERATOR_FORCE_FULL_MACRO宏指定要强制使用全名,我们就可以用简化的宏PG_MAKE_CHANGER、PG_SETGET_x和PG_SIGNAL_x了。

完整代码

源码仅实现了一个属性数据最多支持4个属性值,4个属性值一般能满足绝大多数的场景。加上调用源的标识符,信号中携带的参数达到了5个,过长的参数列表不仅影响代码质量,也会影响信号槽的性能,当然对于现代硬件条件,普通的应用这点影响几乎可以忽略。如果有需要,可以参照代码补充支持更多的属性值。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
////////////////////////////////////////////////////////////////////////////////
//
// MIT License
//
// Copyright (c) 2020 kongdeyou(https://tis.ac.cn/blog/kongdeyou/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

#ifndef PROPGEN_H
#define PROPGEN_H

#define PROPERTY_GENERATOR_SETGET_1(n, n1, t1) \
public: \
void SetProps##n(uintptr_t changer, const std::tuple<t1>& props) \
{ \
prop##n1 = std::get<0>(props); \
emit n##Changed(changer, prop##n1); \
} \
std::tuple<t1> GetProps##n() const \
{ \
return { prop##n1 }; \
} \
void Set##n1(uintptr_t changer, t1 prop##n1) \
{ \
this->prop##n1 = prop##n1; \
emit n##Changed(changer, prop##n1); \
} \
t1 Get##n1() const \
{ \
return prop##n1; \
} \
private: \
t1 prop##n1;

#define PROPERTY_GENERATOR_SETGET_2(n, n1, n2, t1, t2) \
public: \
void SetProps##n(uintptr_t changer, const std::tuple<t1, t2>& props) \
{ \
prop##n1 = std::get<0>(props); \
prop##n2 = std::get<1>(props); \
emit n##Changed(changer, prop##n1, prop##n2); \
} \
std::tuple<t1, t2> GetProps##n() const \
{ \
return { prop##n1, prop##n2 }; \
} \
void Set##n1(uintptr_t changer, t1 prop##n1) \
{ \
this->prop##n1 = prop##n1; \
emit n##Changed(changer, prop##n1, prop##n2); \
} \
t1 Get##n1() const \
{ \
return prop##n1; \
} \
void Set##n2(uintptr_t changer, t2 prop##n2) \
{ \
this->prop##n2 = prop##n2; \
emit n##Changed(changer, prop##n1, prop##n2); \
} \
t2 Get##n2() const \
{ \
return prop##n2; \
} \
private: \
t1 prop##n1; \
t2 prop##n2;

#define PROPERTY_GENERATOR_SETGET_3(n, n1, n2, n3, t1, t2, t3) \
public: \
void SetProps##n(uintptr_t changer, const std::tuple<t1, t2, t3>& props) \
{ \
prop##n1 = std::get<0>(props); \
prop##n2 = std::get<1>(props); \
prop##n3 = std::get<2>(props); \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3); \
} \
std::tuple<t1, t2, t3> GetProps##n() const \
{ \
return { prop##n1, prop##n2, prop##n3 }; \
} \
void Set##n1(uintptr_t changer, t1 prop##n1) \
{ \
this->prop##n1 = prop##n1; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3); \
} \
t1 Get##n1() const \
{ \
return prop##n1; \
} \
void Set##n2(uintptr_t changer, t2 prop##n2) \
{ \
this->prop##n2 = prop##n2; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3); \
} \
t2 Get##n2() const \
{ \
return prop##n2; \
} \
void Set##n3(uintptr_t changer, t3 prop##n3) \
{ \
this->prop##n3 = prop##n3; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3); \
} \
t3 Get##n3() const \
{ \
return prop##n3; \
} \
private: \
t1 prop##n1; \
t2 prop##n2; \
t3 prop##n3;

#define PROPERTY_GENERATOR_SETGET_4(n, n1, n2, n3, n4, t1, t2, t3, t4) \
public: \
void SetProps##n(uintptr_t changer, const std::tuple<t1, t2, t3, t4>& props) \
{ \
prop##n1 = std::get<0>(props); \
prop##n2 = std::get<1>(props); \
prop##n3 = std::get<2>(props); \
prop##n4 = std::get<3>(props); \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
std::tuple<t1, t2, t3, t4> GetProps##n() const \
{ \
return { prop##n1, prop##n2, prop##n3, prop##n4 }; \
} \
void Set##n1(uintptr_t changer, t1 prop##n1) \
{ \
this->prop##n1 = prop##n1; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t1 Get##n1() const \
{ \
return prop##n1; \
} \
void Set##n2(uintptr_t changer, t2 prop##n2) \
{ \
this->prop##n2 = prop##n2; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t2 Get##n2() const \
{ \
return prop##n2; \
} \
void Set##n3(uintptr_t changer, t3 prop##n3) \
{ \
this->prop##n3 = prop##n3; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t3 Get##n3() const \
{ \
return prop##n3; \
} \
void Set##n4(uintptr_t changer, t4 prop##n4) \
{ \
this->prop##n4 = prop##n4; \
emit n##Changed(changer, prop##n1, prop##n2, prop##n3, prop##n4); \
} \
t4 Get##n4() const \
{ \
return prop##n4; \
} \
private: \
t1 prop##n1; \
t2 prop##n2; \
t3 prop##n3; \
t4 prop##n4;

#define PROPERTY_GENERATOR_SIGNAL_1(n, n1, t1) \
void n##Changed(uintptr_t changer, t1 prop##n1);

#define PROPERTY_GENERATOR_SIGNAL_2(n, n1, n2, t1, t2) \
void n##Changed(uintptr_t changer, t1 prop##n1, t2 prop##n2);

#define PROPERTY_GENERATOR_SIGNAL_3(n, n1, n2, n3, t1, t2, t3) \
void n##Changed(uintptr_t changer, t1 prop##n1, t2 prop##n2, t3 prop##n3);

#define PROPERTY_GENERATOR_SIGNAL_4(n, n1, n2, n3, n4, t1, t2, t3, t4) \
void n##Changed(uintptr_t changer, t1 prop##n1, t2 prop##n2, t3 prop##n3, t4 prop##n4);

#define PROPERTY_GENERATOR_MAKE_CHANGER(c) reinterpret_cast<uintptr_t>(c)

#ifndef PROPERTY_GENERATOR_FORCE_FULL_MACRO
#define PG_SETGET_1 PROPERTY_GENERATOR_SETGET_1
#define PG_SETGET_2 PROPERTY_GENERATOR_SETGET_2
#define PG_SETGET_3 PROPERTY_GENERATOR_SETGET_3
#define PG_SETGET_4 PROPERTY_GENERATOR_SETGET_4
#define PG_SIGNAL_1 PROPERTY_GENERATOR_SIGNAL_1
#define PG_SIGNAL_2 PROPERTY_GENERATOR_SIGNAL_2
#define PG_SIGNAL_3 PROPERTY_GENERATOR_SIGNAL_3
#define PG_SIGNAL_4 PROPERTY_GENERATOR_SIGNAL_4
#define PG_MAKE_CHANGER PROPERTY_GENERATOR_MAKE_CHANGER
#endif

#endif // PROPGEN_H

协议

本文以上内容遵循CC BY-ND 4.0协议,署名-禁止演绎。

本文中所提供的源代码遵循MIT开源协议。 代码托管于:https://github.com/KondeU/QtPropGen
代码仓中含有演示示例代码。

转载请注明出处:https://tis.ac.cn/blog/kongdeyou/实现自动生成变量的settergetter_qt版/
署名作者:kongdeyou

后记

2020年12月29日 周二 南京大雪纷飞,一天的光景大地已是白皑皑的一片。

原始链接:https://blog.kdyx.net/blog/kongdeyou/%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E5%8F%98%E9%87%8F%E7%9A%84settergetter_qt%E7%89%88/

版权声明: "CC BY-NC-ND 4.0" 署名-不可商用-禁止演绎 转载请注明原文链接及作者信息,侵权必究。

×

喜欢或有帮助?赞赏下作者呗!