提高可读性和可维护性:省略许多变量声明的< >可能吗?

Increase readability & maintainability : omit < > for many variable declaration possible?

本文关键字:lt gt 声明 变量 可读性 可维护性 许多      更新时间:2023-10-16

这个问题看起来很抽象。我将举个例子来问。

引言

假设我有许多类型的游戏对象。

它们是子弹,火箭,敌人,区域。

它们都是精心制作的&已删除&由池管理,例如

Pool<Bullet> poolBullet ; 
Pool<Rocket> poolRocket ;  

游戏逻辑将以Pool_Handle<xxx>,例如

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,甚至比任何游戏对象都更习惯。这可能是我现在打字最快的一个词。

问题

如何将可读性和可维护性保持在旧代码的同等水平,如果可能的话,如何减少变量模板的键入时间?

我不希望有一个完美的答案,因为我找到的每一个解决方案都有一些权衡。

我糟糕的解决方案

/**  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->MetalBullet,我还必须手动将补救行更改为H_MetalBullet。

  • 因此,它降低了整体可维护性。

有更好的方法吗?

(编辑)你可以假设c++11,c++14,不管怎样
(编辑)独立于平台的解决方案是有利的。

编辑1(进一步澄清第二个解决方案不完美的原因)

在第二个解决方案中;另一个";声明一个新类之后的行。

线路是。。。

using H_Bullet  = Pool_Handle<H_Bullet>; 

我应该把它放在哪里?

  1. 内部Bullet.h;

    这会造成不好的耦合,因为Bullet根本不应该知道手柄。

  2. 在每个游戏逻辑文件所包含的某个高级标头内;

    结果是,有两个不同的地方对Bullet有一些定义
    更多位置->可维护性较低。

这两个地方都会产生一些小缺点:当我调用一些自动重构时,我必须调用两次,分别是BulletH_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();
}

查看成功编译结果

选项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(auto const& z: rocket->zones) {
z.functionB();
}

一般来说,我强烈建议查看范围库,例如Boost.range或Eric Niebler的range-v3。这使您能够编写非常精简但具有描述性的代码,例如

// call functionB on all zones and sum the results
auto sumOfResult = accumulate( rocket->zones | transformed( [](auto const& z) { return functionB(z);}) );

只向前声明您感兴趣的类型的头有什么问题?只有当您向系统中添加需要句柄的新类型时,它才会发生变化,如果您重命名/添加某些内容,它就会正常中断。

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

唯一的另一种选择是反转并转发声明池和句柄,并将其包含/添加到引入需要句柄的新类型的每个头中。