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的引用。
Cartridge将由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。
答案 0 :(得分:8)
我目前正在质疑这个设计,因为这3个课程 (盒式磁带,CPU,MMU)只会被实例化一次。
没有用户会想要,比如看他们的朋友玩游戏吗?流式传输命令并在主机上模拟它们比流式传输视频更具带宽效率。从来没有人愿意和朋友一起做“链接模式”的事情。从来没有,先生。
也就是说,这是你的应用程序的某种基石,实际上是单身人士的理由,这是完全错误的。您当前的设计和功能并不需要更多,但完全没有理由锁定自己。其次,即使它们只能被实例化一次,永远,这可以通过一些private
访问控制有效地实施,并且绝对不需要全局状态。
简而言之,单身人士是现代节目中最糟糕的想法之一。不要使用它。
如果这个最好,我应该继续使用shared_ptr吗?我读过这个 它应该谨慎使用,但在这里,它似乎有意义 所有3个班级都相互依赖。
呃,没有。为什么要使用指针?只需将它们作为成员变量直接使用。跨类依赖项没有理由使用shared_ptr
,只需使用T *。
答案 1 :(得分:1)
除了使Global对象/变量更加花哨之外,没有什么理由使用Singleton。如果你找到一个好的设计来避免全局变量,那么它很容易让它比单身人士更好。
一些(有争议的)例外是记录器类或对资源的访问,这些资源在系统中可能本质上是全局的,如共享内存。
就shared_ptr与unique_ptr的使用而言。坚持使用unique_ptr,除非你有充分的理由需要shared_ptr。这里它们在功能上是等价的,但unique_ptr清楚地表明Gameboy
类拥有三个对象,每个对象各有一个实例。另外,正如其他一些答案所述,您可以完全避免使用指针,但使用它们也有好处。
我应该把它放在我的描述中,但正如你从init列表中看到的那样,MMU需要一个指向Cartridge的指针,CPU需要一个指向MMU的指针。使用静态分配的对象有更好的方法吗?
一种方法是抛弃init列表,因为你的构造函数有点复杂并且明确地处理顺序。
GameBoy()
:cart(nullptr),
mmu(nullptr),
cpu(nullptr)
{
cart = new Cartrige();
mmu = new MMU(cart);
cpu = new CPU(mmu);
}
答案 2 :(得分:0)
一个好的设计可以让你同时运行多个仿真,即使这个要求在实践中从未发生过。
我认为您有两种选择,具体取决于依赖项的工作方式。如果mmu需要墨盒,为什么不让墨盒成为mmu的一员呢?而mmu是cpu的成员。或者如果你想让它们在gameboy中都可以访问,我认为这三个人都会和gameboy一样长寿。将它们作为unique_ptr或full-members(没有指针)存储在gameboy中,通过常规指针将它们传递给子类,然后当gameboy被销毁时它们将被删除。
答案 3 :(得分:0)
变化是不变的,您应该确信在您想要更改代码的时候会到来。这意味着如果您当前的设计不是需要单身,那么就没有理由限制自己。当前的实现允许更多的可扩展性。
至于shared_ptr
,当您需要指向相同,共享实例的多个指针时,可以使用它。例如,假设您有3个类A
,B
和C
。 B
和C
都有指向A
实例的指针,B
和C
之间共享一个实例,因此只应在之后删除 B
和C
都已删除。在该示例中,您应该使用shared_ptr
。否则你应该使用unique_ptr
。因此,您不应该使用shared_ptr
。实际上,如果cart
,mmu
和cpu
是具体类,而不是接口/基类,则不应使用任何类型的指针。
编辑:
为什么GameBoy
类需要Cartridge
,MMU
和CPU
的实例?为什么不只是CPU
创建自己的MMU
创建自己的Cartridge
?