在多个类

时间:2015-06-01 13:52:47

标签: c++ 6502

我正在将NMOS6502仿真器重构为多个类。我想知道是否有一个"面向对象"定义函数跳转表的方法。基本上,我已经定义了单独的指令类来对相关的cpu操作组进行分类 - 比如" CStackInstHandler"或" CArithmeticInstHandler"它将引用一个cpu对象。每个指令类都是从抽象指令类派生的。每个派生的指令类都有一组函数,它们将使用cpu对象的公共接口来改变cpu状态,例如:

uint8_t opcode = _memory->readMem(_cpu->getProgramCounter());
AInstructionHandler* _handler = _cpu->getInstHandler(opcode);
_handler->setCpu(&cpu);
_handler->setMemory(&memory);
_handler->execute(opcode);    

问题在于,在运行时,需要使用操作码来确定指令处理程序以及为该处理程序定义的相应成员函数。

所以我们有 - 从内存中读取操作码,cpu使用一个表将操作码映射到指令处理程序类型,然后指令处理程序使用相同的操作码来选择正确的函数。每条指令都会覆盖"执行"功能例如:

void CBranchInstHandler::execute() {
    switch(_opcode) {
        case 0x90:
            this->BCC();
            break;
        case 0xb0:
            this->BCS();
            break;
        case 0xf0:
            this->BEQ();
            break;
        case 0x30:
            this->BMI();
            break;
        case 0xd0:
            this->BNE();
            break;
        case 0x10:
            this->BPL();
            break;
        default:
            break;
    }

}

void CBranchInstHandler::BCC() {
    uint16_t address = this->getAddress();
    if(!_cpu->isCarry()) {
        uint16_t pc = _cpu->getPC();
        pc += address;
        _cpu->setPC(pc);
    }
}

/*more instruction specific functions...*/

我最终得到了两个查找,其中一个是多余的。一个选择处理程序,另一个选择处理程序函数。我觉得这是完成这项任务的错误方法,但我不确定是否只是将其转换为非成员函数组。

我想知道是否有人对这个问题有所了解。它基本上归结为希望将一个类重构为更小的bite(cpu类,其中指令成员函数被重构为cpu类和指令类),但所有组件都是如此相互关联,以至于我最终不得不重复自己。引入了冗余。

非面向对象的解决方案是让这些指令成为接受cpu引用的非成员函数。然后,将定义函数跳转表,将通过操作码查找和索引指令并执行。

对于对象来说,这似乎并不实用。我可以将所有指令设置为静态或其他东西,但这似乎忽略了这一点。

任何有关切向相关问题的见解或信息都会非常有用。

感谢。

3 个答案:

答案 0 :(得分:1)

您可以使用指向类成员函数/方法的指针:

void (CBranchHandlerBase::*)();

用于存储指向应该为给定_opcode调用的方法的指针。

map<uint8_t, void (CBranchHandlerBase::*)()> handlers;
handlers[0x90] = &BCC;
handlers[0xb0] = &BCS;
...

上面的代码应该在处理程序的基类内的initialize部分/方法中提供。当然,必须将BCC,BCS等声明为纯虚方法才能使该方法有效。

然后代替你的开关:

void CBranchHandlerBase::execute() {
    (this->*handlers[_opcode])();
}

请注意,execute是在基类中定义的(并且它不必是虚拟的!因为每个Handler都具有与execute方法相同的功能)。

修改:出于效率原因,地图实际上可以由尺寸为2^(8*sizeof(uint8_t))的矢量或数组替换

答案 1 :(得分:1)

如果我理解,你正在做的是为每种类型的指令(分支,算术,加载,存储等)创建一个类,然后在你正在为各个指令编写成员函数的那些 - c.f.你有一个“CBranchInstrHandler”处理“携带分支”,“零分支”等?

完全面向对象的方法是将子类化扩展为单个指令。

class CBranchInstrHandler { virtual void execute() = 0; };
class CBranchOnCarryClear : public CBranchInstrHandler {
    void execute() override {
        ...;
    }
};
class CBranchOnCarrySet : public CBranchInstrHandler {
    void execute() override {
        ...;
    }
};

现在,您可以一次性查看您的说明,但您需要对所有这些进行一对一的映射。

switch (opCode) {
    case 0x90: return .. CBranchOnCarryClear .. ;
    case 0xB0: return .. CBranchOnCarrySet .. ;
}

elipsis在那里是因为我不确定你是如何获得指向你的CBranchInstrHandler的指针;我猜它们是静态的,而且你不是new每一条指令。

如果它们是无数据的,您可以按值将它们作为函数对象返回:

struct Base { virtual void execute() { /* noop */ } };
struct Derived { void execute(override) { ... } };

Base getHandler(opcode_t opcode) {
    if (opcode == 1) { return Derived(); }
}

但我怀疑你可能想要参数和存储状态,在这种情况下,按值返回可能会导致切片。

当然,如果你使用的是C ++ 11,你可以使用lambdas:

switch (opCode) {
    case 0x90: return [this] () {
        ... implement BCC execute here ...
    };
    case 0xB0: return [this] () {
        ... implement BCS execute here ...
    }
}

答案 2 :(得分:1)

我要将答案推广到答案:正如您所说,面向对象的解决方案是让孩子们负责决定他们回应哪些操作码。

我建议最简单的方法是尝试构建一个两阶段switch,但只是将每个操作码路由到每个孩子并让孩子做出贡献或者不。这是最不可行的解决方案。

如果您需要优化,那么最简单的方法就是重新制定:

void CBranchInstHandler::execute() {
    switch(_opcode) {
        case 0x90:
            this->BCC();
            break;
            ... etc ...
    }
}

要:

FuncPtr CBranchInstHandler::execute() {
    switch(_opcode) {
        case 0x90:
            return BCC;
            ... etc ...
    }
    return NULL;
}

因此每个execute返回它是否实际上处理了该操作码。

在父类中,您可以简单地将一个表从操作码保存到函数指针。数组会做。该表最初将包含NULL个。

执行操作码时,请在表格中查找处理程序。如果处理程序在那里,请调用它并继续。如果没有,那么依次为每个孩子调用execute,直到有人返回一个处理程序,然后将它们放入表中然后调用它。因此,您可以在运行时及时构建它。每个操作码的第一次运行需要稍长一点,但随后您将获得相当于跳转表的内容。

这样做的好处是它允许有关子句处理内容的信息与语法的实际处理密切相关,从而减少代码开销和错误概率。