提高可读性&可维护性:省略< >可以进行多种变量声明吗?

时间:2016-06-05 14:10:10

标签: c++ templates maintainability code-readability

这个问题似乎非常抽象。我会以一个例子来询问。

简介

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

他们是子弹,火箭,敌人,区域......

他们都很好地创造了&删除&由池管理,例如

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

游戏逻辑将以Pool_Handle&lt;的形式管理对象。 xxx&gt;,例如

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,Rocket,......等声明一行

  • 如果有新类型的游戏对象,我将不得不手动添加另一行。

  • 如果重命名游戏对象,例如子弹 - &gt; MetalBullet, 我还必须手动将补救线更改为H_MetalBullet。

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

有没有更好的方法?

(编辑)你可以假设c ++ 11,c ++ 14,无论如何 (编辑)独立于平台的解决方案是有利的。

编辑1(为了澄清第二种解决方案不完善的原因)

在第二个解决方案中,我必须在声明一个新类后添加“另一个”行。

这条线是......

using H_Bullet  = Pool_Handle<H_Bullet>; 

我应该把它放在哪里?

  1. 在Bullet.h内;

    它会产生错误的耦合,因为Bullet根本不应该知道Handle。

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

    结果是有两个不同的地方有关于子弹的一些定义 更多地方 - &gt;维护性较差。

  3. 这两个地方都会产生一些小缺点: 当我调用一些自动重构时,我必须在 Bullet H_Bullet 上调用两次。

3 个答案:

答案 0 :(得分:2)

选项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(对于无法访问功能的人):

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();
}

View Successful Compilation Result

答案 1 :(得分:2)

除了使用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.RangeEric 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);}) );

答案 2 :(得分:1)

标题只是向前声明您感兴趣的类型有什么问题?它只会在您需要句柄的系统中添加新类型时更改,如果您重命名/添加某些内容,它将会优雅地中断。

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

唯一的另一个选择是反转事物并转发声明池并处理并包含/添加到每个引入需要句柄的新类型的头文件。