0
class GameBoy{

  public:
    GameBoy()
      :cart(new Cartridge()),
        mmu(new MMU(cart)),
        cpu(new CPU(mmu))
    {}

    void step(); //steps through one instruction
    const CPU::State &getCPUState() const noexcept;
    void loadROM(std::string fileName);

  private:
    std::shared_ptr<Cartridge> cart;
    std::shared_ptr<MMU> mmu;
    std::shared_ptr<CPU> cpu;

  };

我正在编写一个 Game Boy 模拟器,而这个类是 Qt GUI 将与之交互的。也就是说,对于在 GUI 中绘制的每一帧,它将调用 GameBoy::step()。显然,它还没有完成,因为我还没有开始在 GPU 上工作。

我目前正在质疑这种设计,因为这 3 个类(Cartridge、CPU、MMU)只会被实例化一次。单例会比这更好,还是当前的设计是最好的?

如果这个最好,我应该留在 shared_ptr 吗?我读过它应该谨慎使用,但在这里,这似乎是有道理的,因为所有 3 个类都相互依赖。

非常感谢!

编辑:人们问我为什么要使用指针。从初始化列表中可以看出,MMU 需要对 Cartridge 的引用,而 CPU 需要对 MMU 的引用。

该墨盒将被 GameBoy::loadROM() 和大多数 MMU 方法使用。我同意 MMU 可能不需要归 GameBoy 所有,但它将在 CPU 和 GPU 之间共享(我认为)。

在下面的示例中,是否有更好的替代购物车共享指针的方法?

类之间的示例用法:

byte MMU::readByte(word address) {
            ....
            value = cart->readROM(address);
            ....
        }

void GameBoy::loadROM(std::string fileName){
            cart->loadROM(fileName);
}

编辑2:谢谢大家!我有最后一个问题。如果GPU和CPU都使用MMU,而GameBoy同时使用GPU和CPU,哪种设计更好:

class GameBoy{

public:
GameBoy()
 : CPU(&mmu), gpu(&mmu)
...

private:
MMU mmu;  //never used by GameBoy
CPU cpu;  //CPU and GPU used in GameBoy
GPU gpu;

};

class CPU{
...

private:
MMU *mmu;
};

class GPU{

...

private:
MMU *mmu;

};

或者:

class GameBoy{

public:
GameBoy()
 : CPU(), gpu(&cpu.getMMU())
...

private:
CPU cpu; //CPU and GPU used in GameBoy
GPU gpu;

};

class CPU{
...

private:
MMU mmu;  //arbitrarily chosen owner.  GPU could have been the owner
};

class GPU{

...

private:
MMU *mmu;

};

我更喜欢第一个,因为它看起来更一致,但 GameBoy 从不使用 MMU。

4

4 回答 4

8

我目前正在质疑这种设计,因为这 3 个类(Cartridge、CPU、MMU)只会被实例化一次。

没有用户会想,比如说,看他们的朋友玩游戏?流式传输命令并在主机上模拟它们比流式传输视频更节省带宽。从来没有人愿意和朋友一起做那种“链接模式”的事情。从来没有,先生。

也就是说,认为这是您的应用程序的某种基石,实际上可以证明单例的想法是完全错误的。您当前的设计和功能所要求的仅此而已,但绝对没有任何理由将自己锁定。其次,即使它们只能实例化一次,也可以通过某些private访问控制有效地强制执行,并且绝对会不需要全局状态。

简而言之,单例是现代编程中最糟糕的想法之一。永远不要使用一个。

如果这个最好,我应该留在 shared_ptr 吗?我读过它应该谨慎使用,但在这里,这似乎是有道理的,因为所有 3 个类都相互依赖。

呃没有。为什么要使用指针?只需将它们直接作为成员变量即可。没有理由使用跨类依赖shared_ptr,只需使用 T*。

于 2013-08-19T02:11:31.503 回答
1

除了使全局对象/变量更花哨之外,几乎没有理由使用单例。如果您找到了避免全局变量的好设计,那么很容易打赌它比单例更好。

一些(有争议的)异常是记录器类或对系统中可能固有全局的资源的访问,例如共享内存。

至于 shared_ptr 与 unique_ptr 的使用。坚持使用 unique_ptr,除非您有充分的理由需要 shared_ptr。在这里,它们在功能上是等效的,但 unique_ptr 清楚地表明Gameboy该类拥有三个对象,每个对象都有一个实例。同样如其他一些答案所述,您可以完全避免使用指针,但使用它们也有好处。

我应该把它放在我的描述中,但是从初始化列表中可以看出,MMU 需要一个指向 Cartridge 的指针,而 CPU 需要一个指向 MMU 的指针。有没有更好的方法来处理静态分配的对象?

一种方法是放弃初始化列表,因为您的构造函数有点复杂并明确处理顺序。

GameBoy()
  :cart(nullptr),
    mmu(nullptr),
    cpu(nullptr)
{
    cart = new Cartrige();
    mmu = new MMU(cart);
    cpu = new CPU(mmu);
}
于 2013-08-19T02:05:09.577 回答
0

一个好的设计可以让您同时运行多个仿真,即使在实践中从未出现过需求。

我认为您有两个选择,具体取决于依赖项的工作方式。如果 mmu 需要一个弹药筒,为什么不让弹药筒成为 mmu 的成员呢?而mmu是cpu的一员。或者,如果您想让它们都可以在 gameboy 中访问,我认为这三个人都会像 gameboy 一样长寿。将它们作为unique_ptr 或full-members(不带指针)存储在gameboy 内部,通过常规指针将它们传递给子类,然后在gameboy 销毁时将它们删除。

于 2013-08-19T02:12:55.107 回答
0

变化是不断的,你应该相信有一天你会想要改变你的代码。这意味着如果您当前的设计不需要单例,那么没有理由将自己限制在此范围内。当前的实现允许更多的可扩展性。

至于,shared_ptr当您需要多个指向同一个共享实例的指针时使用它。例如,假设您有 3 个类AB并且C. B并且C两者都有一个指向 的实例的指针,其中一个实例在andA之间共享,因此应该只在两者之后删除and被删除。在该示例中,您应该使用. 否则你应该使用. 因此,您不应该使用. 实际上,如果和是具体类,而不是接口/基类,则不应使用任何类型的指针。BC BCshared_ptrunique_ptrshared_ptrcartmmucpu

编辑:

为什么GameBoy类需要 Cartridge,MMU和的实例CPU?为什么不只是一个CPU创造它自己MMU的创造它自己的东西Cartridge

于 2013-08-19T02:27:31.100 回答