5

我有一个国际象棋变体引擎,可以与普通国际象棋一起下自杀棋和失败者棋。随着时间的推移,我可能会为我的引擎添加更多变体。该引擎完全用 C++ 实现,并正确使用了 OOP。我的问题与这种变体引擎的设计有关。

最初,该项目作为一个仅用于自杀的引擎开始,随着时间的推移我添加了其他口味。为了添加新的变体,我首先尝试在 C++ 中使用多态性。例如,一个MoveGenerator抽象类有两个子类SuicideMoveGeneratorNormalMoveGenerator并且根据用户选择的游戏类型,工厂将实例化正确的子类。但我发现这要慢得多——显然是因为实例化包含虚函数的类和在紧密循环中调用虚函数都非常低效。

但后来我想到使用具有模板专业化的 C++ 模板来分离不同变体的逻辑,最大限度地重用代码。这似乎也很合乎逻辑,因为动态链接在上下文中并不是真正必要的,因为一旦您选择了游戏类型,您基本上会坚持到游戏结束。C++ 模板专业化正是提供了这一点——静态多态性。模板参数是SUICIDEorLOSERSNORMAL

enum GameType { LOSERS, NORMAL, SUICIDE };

因此,一旦用户选择了游戏类型,就会实例化适当的游戏对象,并且从那里调用的所有内容都将被适当地模板化。例如,如果用户选择自杀棋,让我们说:

ComputerPlayer<SUICIDE>

对象被实例化,并且该实例化基本上与整个控制流静态链接。中的函数ComputerPlayer<SUICIDE>将与MoveGenerator<SUICIDE>,Board<SUICIDE>等一起使用,而相应的函数NORMAL将适当地工作。

总的来说,这让我在一开始就实例化了正确的模板化专业类,并且在任何地方都没有任何其他if条件,整个事情都很完美。最好的事情是根本没有性能损失!

然而,这种方法的主要缺点是使用模板会使您的代码更难阅读。如果处理不当,模板专业化也会导致重大错误。

我想知道其他变体引擎作者通常会为逻辑分离做什么(良好的代码重用)?我发现 C++ 模板编程非常合适,但如果有更好的东西,我很乐意接受。特别是,我检查了 HG Muller 博士的 Fairymax 引擎,但它使用配置文件来定义游戏规则。我不想这样做,因为我的许多变体都有不同的扩展名,并且通过使其通用到配置文件的级别,引擎可能不会变得强大。另一种流行的引擎 Sjeng 到处都是if条件,我个人觉得这不是一个好的设计。

任何新的设计见解都会非常有用。

4

2 回答 2

6

“在紧密循环中调用虚函数效率低下”

实际上,如果这导致任何真正的膨胀,我会非常惊讶,如果循环的所有变量都具有相同的动态类型,那么我希望编译器从其 L1 缓存中获取相应的指令,因此不会受到太大影响。

但是有一点让我担心:

“显然是因为实例化包含虚函数的类 [is] 非常低效”

现在……我真的很惊讶。

用虚函数实例化一个类的成本与用任何虚函数实例化一个类的成本几乎没有区别:它多了一个指针,仅此而已(在流行的编译器上,它对应于_vptr)。

我猜你的问题出在其他地方。所以我要大胆猜测:

  • 您是否有机会进行大量动态实例化?(打电话new

如果是这种情况,您将通过删除它们获得很多收益。

有一种称为设计模式的设计模式Strategy非常适合您的具体情况。实际上,这种模式的想法类似于使用虚函数,但它实际上将这些函数外部化。

这是一个简单的例子:

class StrategyInterface
{
public:
  Move GenerateMove(Player const& player) const;
private:
  virtual Move GenerateMoveImpl(Player const& player) const = 0;
};

class SuicideChessStrategy: public StrategyInterface
{
  virtual Move GenerateMoveImpl(Player const& player) const = 0;
};

// Others

实施后,您需要一个函数来获得正确的策略:

StrategyInterface& GetStrategy(GameType gt)
{
  static std::array<StrategyInterface*,3> strategies
    = { new SuicideChessStrategy(), .... };
  return *(strategies[gt]);
}

最后,您可以委派工作,而无需对其他结构使用继承:

class Player
{
public:
  Move GenerateMove() const { return GetStrategy(gt).GenerateMove(*this); }

private:
  GameType gt;
};

成本与使用虚拟函数非常相似,但是您不再需要为游戏的基本对象动态分配内存,而且堆栈分配要快很多。

于 2010-09-12T12:46:00.803 回答
0

我不太确定这是否合适,但您可以通过CRTP实现静态多态性,只需对您的原始设计稍作修改。

于 2010-09-12T07:06:42.773 回答