这个问题似乎非常抽象。我会以一个例子来询问。
假设我有很多类型的游戏对象。
他们是子弹,火箭,敌人,区域......
他们都很好地创造了&删除&由池管理,例如
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,无论如何 (编辑)独立于平台的解决方案是有利的。
在第二个解决方案中,我必须在声明一个新类后添加“另一个”行。
这条线是......
using H_Bullet = Pool_Handle<H_Bullet>;
我应该把它放在哪里?
在Bullet.h内;
它会产生错误的耦合,因为Bullet根本不应该知道Handle。
在每个游戏逻辑文件包含的某个高级标题内;
结果是有两个不同的地方有关于子弹的一些定义 更多地方 - &gt;维护性较差。
这两个地方都会产生一些小缺点: 当我调用一些自动重构时,我必须在 Bullet 和 H_Bullet 上调用两次。
答案 0 :(得分:2)
使用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
使用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();
}
答案 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.Range或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);}) );
答案 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>;
唯一的另一个选择是反转事物并转发声明池并处理并包含/添加到每个引入需要句柄的新类型的头文件。