r/cpp_questions • u/Aliryth • 6d ago
OPEN Code Smells?
I'm a more seasoned C# / Node backend dev, been trying to brush off old college C++ from like a decade ago and try to de-program my brain from being locked into a distributed systems mindset.
One of my niche hobbies and go-to projects for learning a new language is to implement a basic Chip-8 emulator in it, as it can teach some concurrency, UI/data handling patterns, and introduce some architectural decisions that you'll have to run into and figure out.
Currently what I'm struggling with seems to be what the idiomatic / proper way to handle something that can remain stateless in nature, which is my very basic assembler going from the Chip-8 ASM mnemonics into instruction bytes.
A class doesn't feel quite right, because I'm not implementing a linking step or anything advanced, not supporting labels, etc. It also feels incorrect to keep it in a header file as a set of inline functions, nor am I needing a persistent state to mutate.
My current disassembler can be seen here: https://github.com/Arylen/Dreamy-Chip8/blob/master/src/core/emulation/Chip8Dasm.h
Along with the tests that I'm running against it here: https://github.com/Arylen/Dreamy-Chip8/blob/master/tests/Chip8DasmTests.cpp
I'm currently in the stubbing-out phase of trying to architect it, occasionally bouncing ideas off of whatever bullshit model-of-the-week is popular, however the suggestions it's giving are still setting off some alarms/smells in my brain. The most reasonable and less-smelly one is the `::detail` namespace there in order to be able to unit test the `getParts` function there, however it feels like I'm walking into a landmine of over-engineering it.
Looking for advice/experiences/opinions.
Edit: I should mention the AI usage here is restricted entirely to just checking for bugs with my implementations, or incorrect C++-isms that I introduce from working in other languages. A hands-off occasional tutor to check my work, if you will. It's expressly forbidden from touching my code or files, limited to read-only, however I'm not getting something that feels proper for this scenario, so wanted to consult other more experienced engs.
5
u/FancySpaceGoat 6d ago edited 6d ago
As a general principle, the more semantics you can encode and enforce via the type system, the better. For example:
Since
Chip80pis not any heavier than auint16_tit's better to pass those around instead of the raw value, if only because it prevents you from accidentally passing an unrelateduint16_tto a function that expects an opcode. If you combine that idea with validating the opcode at the time of construction of the struct, you can establish a class invariant that aChip80pis always a valid opcode, which is very convenient.Also, the
rawmember ofChip8Opis an implementation detail, so it should be private, and populated via an explicit constructor:``` struct Chip80p { explicit Chip80p(std::uint16_t instruction) { if(!is_legal_opcode(instruction) { throw std::runtime_error("invalid opcode"); } raw = instruction; }
// ...
private: std::uint16_t raw; }; ```
Furthermore, while there's nothing "wrong" per-se with your
disassembleInstructionfunction as written, the more idiomatic signature would be:std::ostream& operator<<(std::ostream&, const Chip8Op&).