C++非模板父类如何条件实例化不同类型成员对象
问题描述
我正在创建两个Player结构体对象,每个对象均包含pawn成员。我希望该成员根据所属Player对象的差异定义为不同类型,使得white.pawn.say()与black.pawn.say()可表现出不同行为。我曾尝试分别实现WhitePawn与BlackPawn类,但暂未找到可支持在类内部实例化该类成员对象的工厂设计模式。
当前编写的代码如下:
template <bool N> struct Pawn : public Piece { Pawn(U64 bb) : Piece(bb, 1) {}; }; template <> struct Pawn<0> : public Piece { void say(U64 x) { std::cout << "White pawns" << (x << 8) << "\n"; } }; template <> struct Pawn<1> : public Piece { void say(U64 x) { std::cout << "Black pawns" << (x >> 8) << "\n"; } }; struct Player { Pawn<color> pawn; // 此处需要编译期常量表达式 Knight knight; Bishop bishop; Player(U64 p, U64 n, U64 b, bool color) : pawn(p) , knight(n) , bishop(b) {}; }; Player white( 0xFF000000000000, // 兵的位棋盘 0x4200000000000000, // 马的位棋盘 0x2400000000000000, // 象的位棋盘 0 // 颜色:白方 ); Player black( 0xFF00, // 兵的位棋盘 0x42, // 马的位棋盘 0x24, // 象的位棋盘 1 // 颜色:黑方 );
我不希望将Player定义为模板结构体,因为后续代码中需要将Player作为统一数据类型使用(例如函数定义int makeMove(Player& own, Player& enemy, U16 move) {...})。现有两个疑问:
- 是否存在方法可将传入的
color参数转换为编译期常量表达式,使编译器可正确识别模板参数? - 针对不同父对象条件实例化不同类型成员对象的需求,最佳实现方案是什么?
解决方案
首先明确:运行时传入构造函数的color参数不可能转换为编译期常量。编译期常量要求值在编译阶段就完全确定,而构造函数传参属于运行时行为,哪怕调用时传入的是字面量0/1,编译器也不会将其识别为可用于模板实参的编译期常量,这条路从机制上走不通。
针对你要保留Player为统一非模板类型的需求,最适配的实现方案是基于虚函数的运行时多态,具体实现如下:
#include <iostream> #include <memory> #include <cstdint> using U64 = uint64_t; using U16 = uint16_t; // 棋子基类 struct Piece { U64 bitboard; int value; Piece(U64 bb, int val) : bitboard(bb), value(val) {} virtual ~Piece() = default; // 基类必须定义虚析构,避免派生类资源泄漏 virtual void say(U64 x) = 0; // 定义统一的虚函数接口 }; // 其他棋子实现 struct Knight : Piece { Knight(U64 bb) : Piece(bb, 3) {} }; struct Bishop : Piece { Bishop(U64 bb) : Piece(bb, 3) {} }; // 白兵实现 struct WhitePawn : Piece { WhitePawn(U64 bb) : Piece(bb, 1) {} void say(U64 x) override { std::cout << "White pawns" << (x << 8) << "\n"; } }; // 黑兵实现 struct BlackPawn : Piece { BlackPawn(U64 bb) : Piece(bb, 1) {} void say(U64 x) override { std::cout << "Black pawns" << (x >> 8) << "\n"; } }; struct Player { std::unique_ptr<Piece> pawn; // 用基类智能指针管理实际的兵对象 Knight knight; Bishop bishop; bool is_white; Player(U64 p, U64 n, U64 b, bool color) : knight(n), bishop(b), is_white(color) { // 构造时根据颜色实例化对应类型的兵 if (color == 0) { pawn = std::make_unique<WhitePawn>(p); } else { pawn = std::make_unique<BlackPawn>(p); } } }; // 完全支持你需要的统一类型传参 int makeMove(Player& own, Player& enemy, U16 move) { // 调用时自动动态绑定到对应颜色兵的say实现 own.pawn->say(own.pawn->bitboard); return 0; } int main() { Player white( 0xFF000000000000, 0x4200000000000000, 0x2400000000000000, 0 ); Player black( 0xFF00, 0x42, 0x24, 1 ); white.pawn->say(white.pawn->bitboard); // 输出:White pawns<对应位移后的值> black.pawn->say(black.pawn->bitboard); // 输出:Black pawns<对应位移后的值> return 0; }
这个方案的特点:
- 完全满足
Player为非模板统一类型的要求,你之前写的makeMove这类函数不需要做任何修改即可正常使用 - 调用
say()方法时自动匹配对应颜色的实现,不需要额外写分支判断 - 后续如果要新增其他兵类型(比如升变后的兵、特殊规则兵),只需要新增继承
Piece的派生类即可,不需要修改Player结构 - 仅存在极微小的虚函数调用开销,对于国际象棋这类应用场景,性能影响可以忽略
如果你坚持要用之前写的模板特化Pawn<0>/Pawn<1>,也可以用std::variant<Pawn<0>, Pawn<1>>作为pawn成员的类型,调用时通过std::visit分发逻辑,但写法比多态方案繁琐,实际运行开销和多态接近,没有特殊需求不推荐使用。
内容的提问来源于stack exchange,提问作者Isaac Chugunov
相关产品推荐
相关产品推荐

