提高可读性和可维护性:省略 < > 是否可以用于许多变量声明?
Increase readability & maintainability : omit < > for many variable declaration possible?
这个问题看起来很抽象。我会通过一个例子来问。
简介
假设我有很多类型的游戏 objects.
它们是 Bullet、Rocket、Enemy、Zone、...。
它们都是由池很好地创建、删除和管理的,例如
Pool<Bullet> poolBullet ;
Pool<Rocket> poolRocket ;
游戏逻辑将以 Pool_Handle 的形式管理 object,例如
Pool_Handle< Bullet > bullet = poolBullet.create() ;
Pool_Handle< Rocket> rocket = poolRocket .create() ;
问题
现在,我看看重构。
例如,如果我的旧代码是...
Bullet* functionA(Rocket* rocket){
for(int n=0;n<rocket->zones.size();n++){
Zone* zone = rocket->zones.get(n);
Bullet* return_value =zone.functionB();
}
}
...它会变成...
Pool_Handle<Bullet> functionA(Pool_Handle<Rocket> rocket){
for(int n=0;n<rocket->zones.size();n++){
Pool_Handle<Zone> zone = rocket->zones.get(n);
Pool_Handle<Bullet> return_value =zone.functionB();
}
}
请注意,它们现在 Pool_Handle 无处不在。
看了几天编辑了这几千行代码,我对Pool_Handle的习惯甚至超过了任何游戏object。可能是我现在打字最快的词了
问题
如何保持旧代码的可读性和可维护性,如果可能,如何减少变量模板的输入时间?
我不期待一个完美的答案,因为我找到的每个解决方案都有一些 trade-off。
我糟糕的解决方案
/** remedy 1 **/
template <class TT> using H = Pool_Handle<TT>; //remedy line
H<Bullet> bullet ; //alleviate symptom, better but not perfect
/** remedy 2 **/
using H_Bullet = Pool_Handle<H_Bullet>; //remedy line
H_Bullet bullet ; //looks good, but have limitations
/** remedy 3 **/
auto bullet; //concise, but can't be used everywhere, sometimes also reduce readability
第二个解决方案似乎不错,但引入了一些限制。
我要为Bullet, Rocket, ...等等逐个声明一行
如果有新类型的游戏object,我就得手动加一行了
如果游戏 object 被重命名,例如子弹 - >金属子弹,
我还必须手动将补救行更改为 H_MetalBullet。
因此,它降低了整体可维护性。
有没有更好的方法?
(编辑)您可以假定 c++11、c++14 等等。
(编辑)独立于平台的解决方案是有利的。
编辑 1(进一步阐明为什么第二种解决方案不完美)
在第二个解决方案中,我必须在声明新 class 后添加“另一行”。
该行是...
using H_Bullet = Pool_Handle<H_Bullet>;
我应该把它放在哪里?
里面 Bullet.h ;
它会造成不良耦合,因为 Bullet 根本不应该知道 Handle。
在每个游戏逻辑文件中包含的一些 high-level header ;
结果是有2个不同的地方对Bullet有一些定义。
更多的地方 -> 更少的可维护性。
这两个地方都会产生一定的小缺点:
当我调用一些自动重构时,我必须在 Bullet 和 H_Bullet.
上调用两次
选项 1
使用 auto
说明符:
auto object = pool.create();
完整示例:
template <typename T>
class Pool {
public:
class Handle {};
Handle create () const {
return Handle();
}
};
class Object {};
int main () {
Pool<Object> pool;
auto object = pool.create();
}
View Successful Compilation Result
选项 2
使用 typedef
(对于无法使用 c++11 功能的用户):
Object::Handle object = pool.create();
完整示例:
template <typename T>
class Pool {
public:
class Handle {};
Handle create () const {
return Handle();
}
};
class Object {
public:
typedef Pool<Object>::Handle Handle;
};
int main () {
Pool<Object> pool;
Object::Handle object = pool.create();
}
除了使用 auto
,您还可以受益于使用 STL 算法而不是手写循环。在您的示例中,而不是
Pool_Handle<Bullet> functionA(Pool_Handle<Rocket> rocket){
for(int n=0;n<rocket->zones.size();n++){
Pool_Handle<Zone> zone = rocket->zones.get(n);
Pool_Handle<Bullet> return_value =zone.functionB();
}
}
你可以直接使用std::for_each
std::for_each( rocket->zones.begin(), rocket->zones.end(),
[](auto const& z) {z.functionB();} );
对于这个简单的例子,C++11 for-each 循环可能更好:
for(auto const& z: rocket->zones) {
z.functionB();
}
总的来说,我会强烈建议查看范围库,例如Boost.Range or Eric Niebler's range-v3。这使您能够编写非常简洁但描述性强的代码,例如
// call functionB on all zones and sum the results
auto sumOfResult = accumulate( rocket->zones | transformed( [](auto const& z) { return functionB(z);}) );
刚刚向前声明您感兴趣的类型的 header 有什么问题?它只会在您向系统添加需要句柄的新类型时发生变化,并且如果您重命名/添加某些内容,它将正常中断。
types_fwd.hpp
template <typename> Pool;
template <typename> Pool_Handle;
class Bullet;
class Rocket;
using H_Bullet = Pool_Handle<Bullet>;
using H_Rocket = Pool_Handle<Rocket>;
唯一的其他选择是反转事物并向前声明池和句柄,并将其包含/添加到每个 header 中,引入需要句柄的新类型。
这个问题看起来很抽象。我会通过一个例子来问。
简介
假设我有很多类型的游戏 objects.
它们是 Bullet、Rocket、Enemy、Zone、...。
它们都是由池很好地创建、删除和管理的,例如
Pool<Bullet> poolBullet ;
Pool<Rocket> poolRocket ;
游戏逻辑将以 Pool_Handle
Pool_Handle< Bullet > bullet = poolBullet.create() ;
Pool_Handle< Rocket> rocket = poolRocket .create() ;
问题
现在,我看看重构。
例如,如果我的旧代码是...
Bullet* functionA(Rocket* rocket){
for(int n=0;n<rocket->zones.size();n++){
Zone* zone = rocket->zones.get(n);
Bullet* return_value =zone.functionB();
}
}
...它会变成...
Pool_Handle<Bullet> functionA(Pool_Handle<Rocket> rocket){
for(int n=0;n<rocket->zones.size();n++){
Pool_Handle<Zone> zone = rocket->zones.get(n);
Pool_Handle<Bullet> return_value =zone.functionB();
}
}
请注意,它们现在 Pool_Handle 无处不在。
看了几天编辑了这几千行代码,我对Pool_Handle的习惯甚至超过了任何游戏object。可能是我现在打字最快的词了
问题
如何保持旧代码的可读性和可维护性,如果可能,如何减少变量模板的输入时间?
我不期待一个完美的答案,因为我找到的每个解决方案都有一些 trade-off。
我糟糕的解决方案
/** remedy 1 **/
template <class TT> using H = Pool_Handle<TT>; //remedy line
H<Bullet> bullet ; //alleviate symptom, better but not perfect
/** remedy 2 **/
using H_Bullet = Pool_Handle<H_Bullet>; //remedy line
H_Bullet bullet ; //looks good, but have limitations
/** remedy 3 **/
auto bullet; //concise, but can't be used everywhere, sometimes also reduce readability
第二个解决方案似乎不错,但引入了一些限制。
我要为Bullet, Rocket, ...等等逐个声明一行
如果有新类型的游戏object,我就得手动加一行了
如果游戏 object 被重命名,例如子弹 - >金属子弹, 我还必须手动将补救行更改为 H_MetalBullet。
因此,它降低了整体可维护性。
有没有更好的方法?
(编辑)您可以假定 c++11、c++14 等等。
(编辑)独立于平台的解决方案是有利的。
编辑 1(进一步阐明为什么第二种解决方案不完美)
在第二个解决方案中,我必须在声明新 class 后添加“另一行”。
该行是...
using H_Bullet = Pool_Handle<H_Bullet>;
我应该把它放在哪里?
里面 Bullet.h ;
它会造成不良耦合,因为 Bullet 根本不应该知道 Handle。
在每个游戏逻辑文件中包含的一些 high-level header ;
结果是有2个不同的地方对Bullet有一些定义。
更多的地方 -> 更少的可维护性。
这两个地方都会产生一定的小缺点: 当我调用一些自动重构时,我必须在 Bullet 和 H_Bullet.
上调用两次选项 1
使用 auto
说明符:
auto object = pool.create();
完整示例:
template <typename T>
class Pool {
public:
class Handle {};
Handle create () const {
return Handle();
}
};
class Object {};
int main () {
Pool<Object> pool;
auto object = pool.create();
}
View Successful Compilation Result
选项 2
使用 typedef
(对于无法使用 c++11 功能的用户):
Object::Handle object = pool.create();
完整示例:
template <typename T>
class Pool {
public:
class Handle {};
Handle create () const {
return Handle();
}
};
class Object {
public:
typedef Pool<Object>::Handle Handle;
};
int main () {
Pool<Object> pool;
Object::Handle object = pool.create();
}
除了使用 auto
,您还可以受益于使用 STL 算法而不是手写循环。在您的示例中,而不是
Pool_Handle<Bullet> functionA(Pool_Handle<Rocket> rocket){
for(int n=0;n<rocket->zones.size();n++){
Pool_Handle<Zone> zone = rocket->zones.get(n);
Pool_Handle<Bullet> return_value =zone.functionB();
}
}
你可以直接使用std::for_each
std::for_each( rocket->zones.begin(), rocket->zones.end(),
[](auto const& z) {z.functionB();} );
对于这个简单的例子,C++11 for-each 循环可能更好:
for(auto const& z: rocket->zones) {
z.functionB();
}
总的来说,我会强烈建议查看范围库,例如Boost.Range or Eric Niebler's range-v3。这使您能够编写非常简洁但描述性强的代码,例如
// call functionB on all zones and sum the results
auto sumOfResult = accumulate( rocket->zones | transformed( [](auto const& z) { return functionB(z);}) );
刚刚向前声明您感兴趣的类型的 header 有什么问题?它只会在您向系统添加需要句柄的新类型时发生变化,并且如果您重命名/添加某些内容,它将正常中断。
types_fwd.hpp
template <typename> Pool;
template <typename> Pool_Handle;
class Bullet;
class Rocket;
using H_Bullet = Pool_Handle<Bullet>;
using H_Rocket = Pool_Handle<Rocket>;
唯一的其他选择是反转事物并向前声明池和句柄,并将其包含/添加到每个 header 中,引入需要句柄的新类型。