I have written a disassembler for Intel 8080, able to translate binaries into opcodes and parameters. Sorry for posting so much code, but everything posted here is useful for the understanding of the code (and I hope that you will criticize all of it too).
Thanks a lot for your time.

`FileReaderHelper` holds some functions that help with reading and parsing.

*FileReaderHelper.h*

	#include <vector>
	#include <string>

	class FileReaderHelper
	{
	public:
	    static const std::vector<std::string> splitString(const std::string& toSplit, char delimiter);
	    static const int hexStringToInt(const std::string& toSplit);
	    static const std::vector<unsigned char> loadBinaryFile(const std::string& filepath);
	};

*FileReaderHelper.cpp*

	#include "FileReaderHelper.h"
	#include <sstream>
	#include <fstream>
	#include <iterator>

	const std::vector<std::string> FileReaderHelper::splitString(const std::string& toSplit, char delimiter)
	{
	    std::istringstream iss(toSplit);
	    std::vector<std::string> results;
	    std::string s;
	    while (std::getline(iss, s, delimiter)) {
	        results.emplace_back(s);
	    }
	    return results;
	}

	const int FileReaderHelper::hexStringToInt(const std::string& input)
	{
	    std::stringstream  stringToHex;
	    stringToHex << std::hex << input;
	    int hexValue = 0;
	    stringToHex >> hexValue;
	    return hexValue;
	}
	    
	const std::vector<unsigned char> FileReaderHelper::loadBinaryFile(const std::string& filepath)
	{
	    std::ifstream inputStream(filepath, std::ios::binary);
	    std::vector<unsigned char> result(std::istreambuf_iterator<char>{inputStream}, {});
	    return result;
	}

The `OpCodeCatalog` class contains all the OpCodes, parsed from a text file that contains lines like these (it is much more readable with tabs indentation):

    0xc0	RNZ	1	            if NZ, RET 
    0xc1	POP B	1	        C <- (sp)	 B <- (sp+1)	 sp <- sp+2
    0xc2	JNZ $%02x%02x	3	if NZ, PC <- adr 
    0xc3	JMP $%02x%02x	3	PC <= adr
    0xc4	CNZ $%02x%02x	3	if NZ, CALL adr 
    0xc5	PUSH B	1	        (sp-2)<-C (sp-1)<-B	 sp <- sp - 2 
    0xc6	ADI #%02x	2	    Z, S, P, CY, AC	A <- A + byte
    0xc7	RST 0	1	        CALL $0

*OpCodeCatalog.h*

	#include <string>
	#include <vector>

	#include "OpCode.h"

	class OpCodeCatalog
	{
	private:

	public:
	    OpCodeCatalog(const std::string& filename);

	    void loadCatalog(const std::string& filename);
	    const OpCode& getOpCode(int code) const;
	    size_t getOpCodeCount() const noexcept;

	private:
	    std::vector<OpCode> opCodes;
	};

*OpCodeCatalog.cpp*

	#include "OpCodeCatalog.h"

	#include <fstream>
	#include <sstream>
	#include <iterator>
	#include <string>

	#include "File\FileReaderHelper.h"

	OpCodeCatalog::OpCodeCatalog(const std::string& filename)
	{
	    loadCatalog(filename);
	}

	void OpCodeCatalog::loadCatalog(const std::string& filename)    
	{
	    opCodes.clear();
	    std::ifstream input(filename);
	    for(std::string line; std::getline(input, line);)
	    {
	        const auto tokens = FileReaderHelper::splitString(line, '	');

            if (tokens.size() < 3) { 
                throw "Malformed catalog line: " + line; 
            }

	        opCodes.emplace_back(OpCode{    FileReaderHelper::hexStringToInt(tokens[0]),
	                                        std::stoi(tokens[2], nullptr, 10),
	                                        tokens[1] });   
	    }
	}

	const OpCode& OpCodeCatalog::getOpCode(int code) const
	{
	    if (code < 0 || code >= opCodes.size())
	    {
	        throw std::invalid_argument("OpCode value outside the range of the catalog. Value: " + code);
	    }
	    return opCodes[code];
	}

	size_t OpCodeCatalog::getOpCodeCount() const noexcept
	{
	    return opCodes.size();
	}

The `OpCode` class holds the informations for one OpCode: its code and byte count. It can also generate the associated assembly line using the template string.

*OpCode.h*

	#include <string>

	#define OPCODE_TEMPLATE_MAX_LENGTH 32

	class OpCode
	{
	public:
	    OpCode(int code, int byteCount, std::string resultTemplate);

	    constexpr int getCode() const noexcept
	    {
	        return code;
	    }

	    constexpr int getByteCount() const noexcept
	    {
	        return byteCount;
	    }

	    const std::string getGeneratedAsm() const;
	    const std::string getGeneratedAsm(int v1) const;
	    const std::string getGeneratedAsm(int v1, int v2) const;

	private:
	    const int code;
	    const int byteCount;
	    const std::string resultTemplate;
	};

*OpCode.cpp*

	#include "OpCode.h"

	OpCode::OpCode(int code, int byteCount, std::string resultTemplate) :
	    code{ code },
	    byteCount{ byteCount },
	    resultTemplate{ resultTemplate }
	{
	    if (resultTemplate.length() > OPCODE_TEMPLATE_MAX_LENGTH)
	    {
	        throw std::invalid_argument("This template string is too long: " + resultTemplate);
	    }
	}

	const std::string OpCode::getGeneratedAsm() const
	{
	    return resultTemplate;
	}

	const std::string OpCode::getGeneratedAsm(int v1) const
	{
	    char temp[OPCODE_TEMPLATE_MAX_LENGTH];
	    std::snprintf(temp, OPCODE_TEMPLATE_MAX_LENGTH, resultTemplate.c_str(), v1);
	    return std::string(temp);
	}

	const std::string OpCode::getGeneratedAsm(int v1, int v2) const
	{
	    char temp[OPCODE_TEMPLATE_MAX_LENGTH];
	    std::snprintf(temp, OPCODE_TEMPLATE_MAX_LENGTH, resultTemplate.c_str(), v1, v2);
	    return std::string(temp);
	}

The `AssemblyLine` class holds all the information of a line of assembly language (code, parameters, bytecount).

*AssemblyLine.h*

	#pragma once

	#include "Source\OpCode.h"

	class AssemblyLine
	{
	public:
	    AssemblyLine(uint8_t code, uint8_t byteCount);
	    AssemblyLine(uint8_t code, uint8_t byteCount, const uint8_t param1);
	    AssemblyLine(uint8_t code, uint8_t byteCount, const uint8_t param1, const uint8_t param2);

	    uint8_t getParam1() const;
	    uint8_t getParam2() const;
	    uint8_t getCode() const;
	    uint8_t getByteCount() const;

	protected:
	    const uint8_t byteCount;
	    const uint8_t code;
	    const uint8_t param1;
	    const uint8_t param2;
	};

*AssemblyLine.cpp*

	#include "AssemblyLine.h"

	AssemblyLine::AssemblyLine(uint8_t code, uint8_t byteCount) :
	    code(code),
	    byteCount(byteCount),
	    param1(0),
	    param2(0)
	{

	}

	AssemblyLine::AssemblyLine(uint8_t code, uint8_t byteCount, const uint8_t param1) :
	    code(code),
	    byteCount(byteCount),
	    param1(param1),
	    param2(0)
	{

	}

	AssemblyLine::AssemblyLine(uint8_t code, uint8_t byteCount, const uint8_t param1, const uint8_t param2) :
	    code(code),
	    byteCount(byteCount),
	    param1(param1),
	    param2(param2)
	{

	}

	uint8_t AssemblyLine::getParam1() const
	{
	    return param1;
	}

	uint8_t AssemblyLine::getParam2() const
	{ 
	    return param2;
	}

	uint8_t AssemblyLine::getCode() const
	{
	    return code;
	}

	uint8_t AssemblyLine::getByteCount() const
	{
	    return byteCount;
	}

Last but not least, `Disassembler` is the interface that use all the previous classes to provide the user with meaningful information.

*Disassembler.h*

	#pragma once

	#include "Source\OpCodeCatalog.h"
	#include "AssemblyLine.h"

	class Disassembler
	{
	public:
	    Disassembler(const OpCodeCatalog& catalog);
	    const std::vector<AssemblyLine> disasemble(const std::vector<unsigned char>& data);
	    const AssemblyLine disasembleOneCode(const std::vector<unsigned char>& data, size_t codeIndex);

	private:
	    const OpCodeCatalog& catalog;
	};


*Disassembler.cpp*

	#include "Disassembler.h"

	Disassembler::Disassembler(const OpCodeCatalog& catalog) :
	    catalog{ catalog }
	{
	}

	const std::vector<AssemblyLine> Disassembler::disasemble(const std::vector<unsigned char>& data)
	{
	    size_t currentId = 0;
	    std::vector<AssemblyLine> disasembledCode;
	    for (; currentId < data.size() - 1;)
	    {
	        const auto asmLine = disasembleOneCode(data, currentId);
	        currentId += asmLine.getByteCount();
	        disasembledCode.emplace_back(asmLine);
	    }
	    return disasembledCode;
	}

	const AssemblyLine Disassembler::disasembleOneCode(const std::vector<unsigned char>& data, size_t codeIndex)
	{
	    int codeValue = data[codeIndex];
	    const auto& opCode = catalog.getOpCode(codeValue);
	    const auto byteCount = opCode.getByteCount();

	    switch (byteCount) {
	    default:
	    case 1:
	        return AssemblyLine(opCode.getCode(), opCode.getByteCount());
	        break;
	    case 2:
	        return AssemblyLine(opCode.getCode(), opCode.getByteCount(), data[codeIndex + 1]);
	        break;
	    case 3:
	        return  AssemblyLine(opCode.getCode(), opCode.getByteCount(), data[codeIndex + 2], data[codeIndex + 1]);
	        break;
	    }
	}