Доступ к обьекту из функций, не входящих в класс

Есть класс:


class emulZ80cpu { // класс эмулятора процессора
private:

	// переменные

	unsigned char * _pROMzxs; // указатель на начало физической памяти эмулатора ROM
	unsigned short _zxsROMsize; // размер ROM
	unsigned char * _pRAMzxs; // указатель на начало физической памяти эмулатора RAM
	regSetZ80 _mainRegisters;
	regSetZ80 _shadowRegisters;
	t32bool _useMainRegisters = true32t;
	regPair_16t _regPC;
	unsigned short _regSP;
	t32bool _IFF1; // Флаг разрешения прерывания;
	t32bool _IFF2; // Флаг, копия IFF1 во время обработки NMI;
	unsigned char _regR; // Регистр регенерации памяти, 8 бит. Увеличивается на 1 после каждой выборки команды, но инкремент затрагивает только младшие 7 бит, старший бит не меняется и может быть использован в программах
	unsigned char _intI; // Старший байт адреса вектора прерывания в режиме IM 2;
	unsigned char _modeIM; // Режим обработки прерываний. Устанавливается командами IM 0/1/2.

protected:
		// внутренние функции

	// установить/прочитать указатель текущей команды // установка отдельно старшего и младшего байта
	void z80setPC(const unsigned short inPC) {_regPC.valuePair = inPC;};
	void z80setPCH(const unsigned char inPCH) {_regPC.highByte = inPCH;};
	void z80setPCL(const unsigned char inPCL) {_regPC.lowByte = inPCL;};
	unsigned short z80getPC(void) {return _regPC.valuePair;};
	// увеличение PC регистра на единичку / сдвиг вперед/назад на нужное количество позиций
	void z80addPC(void) {++_regPC.valuePair;};
	void z80movePC(const signed short posMove) {_regPC.valuePair += posMove;};
	// установить/прочитать стэковый регистр SP
	void z80setSP(const unsigned short inSP) {_regSP = inSP;};
	unsigned short z80getSP(void) {return _regSP;};
	// поменять местами основной и теневой набор регистров
	void z80changeMainShadowSetRegs(void) {_useMainRegisters = !_useMainRegisters;};
	// вернуть указатель на текущий набор регистров
	regSetZ80 * _z80getCurrentSetRegs(void);
	// прочитать/записать регистры и регистровые пары
	unsigned char z80getOneReg(const tOneRegs selOneReg);
	void z80setOneReg(const tOneRegs selOneReg, const unsigned char valOneReg);
	unsigned short z80getPairReg(const tPairRegs selPairReg);
	void z80setPairReg(const tPairRegs selPairReg, const unsigned short valPairReg);
	// получить байт по адресу спектрума
	unsigned char z80getByteByAddr(unsigned short srcAddr);
	// получить байт по адресу PC // увеличивается PC На 1
	unsigned char z80fetchByteByPC(void);
	unsigned char _ticksByCmd; // длительнойсть текущей команды в тактах z80

public:
		// внешние функции

	// прописывание указателей на физическую память МК
	void z80setMemoryPointers(const unsigned char * pROMzxs, const unsigned short zxsROMsize, const unsigned char * pRAMzxs) {
		_pROMzxs = (unsigned char *)pROMzxs; _zxsROMsize = zxsROMsize; _pRAMzxs = (unsigned char *)pRAMzxs;
	}
	// сброс процессора
	void z80reset(void) {
		z80setPC(0); _IFF1 = false32t; _IFF2 = false32t; _regR = 0;	_intI = 0; _modeIM = 0;
	}
	// работа процессора
	void z80run(void);
};

к нему будет куча функций, обращающихся к компонентам класса, но поскольку их много, хотел бы их отделить от описания класса, т е сделать их типа внешними.
Например функция:


void z80_cmd_00_NOP(emulZ80cpu * cpu) { // пустая команда
	cpu->_ticksByCmd = 4; // длительность команды в тактах z80
}

компилятор конечно ругается:

../Src/z80commands.cpp: In function ‘void z80_cmd_00_NOP(emulZ80cpu*)’:
../Src/z80commands.cpp:11:14: error: ‘unsigned char emulZ80cpu::_ticksByCmd’ is protected within this context
11 | cpu->_ticksByCmd = 4; // длительность команды в тактах z80
| ^~~~~~~~~~~

можно конечно все в public описать и тогда будет работать, но как то это не красиво…

Есть ли возможность как то или внешние функции подцепить или что то другое?

Странные у Вас желания.
Но если прям “хочу, не могу”, можно эти функции запихнуть в отдельный класс и сделать этот класс другом для emulZ80cpu

Ну, вариантов-то over-8k, смотря что на самом деле нужно.

  1. можно все внешние функции, которым нужен доступ к внутренней кухне класса, описать как его друзей (кл. слово friend);
  2. можно их все собрать в некий отдельный класс (как статические) и описать этот внешний класс другом основного класса;
  3. можно их все собрать в некий класс (как статические) и основной класс пронаследовать от того;
  4. можно все поля, которые требуются внешним функциям, снабдить public геттерами (и, если надо, сеттерами).

Могу предложить ещё 100500 вариантов. Ту как угодно можно делать. Вопрос – что удобнее и насколько параноидальна иде спрятать всё внутрь и чужих не пущать.

ох…
Как много вариантов, спасибо!
Пошел читать…

Да, мысль “в лоб” сделать наследника этого класса была и запихнуть туда все команды, но плодить сущности тоже не хотелось бы.

Обязательно обратите внимание вот на что: для тех полей, которые по жизни могут изменяться внутренними функциями класса, но не должны изменяться внешними, лучше объявлять их (поля) private, а доступ к ним посторонних функций оформлять public- геттерами у которых указан атрибут const. Это гарантирует, что ни одна зараза не сможет изменить поля извне.

1 лайк

в моем конкретном случае как бы не стоит задача сильно ограничить доступы к полям и/или делать потом наследуемые классы.

Основная задача разделить команды и основной объект по разным файлам, что бы читабельно было, не в одной куче.

Остановился на варианте объявления friend класса.
Т е этот дружественный клас может и должен иметь доступ ко всем полям/данным основного класса, мне не нужно его в чем то ограничивать.

Тогда в этом дружественном классе делайте Ваши функции static, иначе придётся экземпляр этого класса создавать.

2 лайка

как бы один из самых очевидных методов вытекает из сообщения компилятора.

Если вы хотели иметь доступ к члену класса, нафига вы его protected описали?

А так, в дополнение к описанному Евгением, можно просто накидать кучу функций, обращающихся к методам и членам класса. Например по типу, как вы работаете с любой библиотекой ардуино

3 лайка

Просьба разьяснить про экземпляр класса, дружеский класс я сделал, все работает.
Но как к нему обратиться из функций основного объекта не создавая экземпляр?

class emulZ80cmd { // класс команд процессора
private:
	static void z80_cmd_00_NOP(emulZ80cpu * cpu); // пустая команда
	static void z80_cmd_01_11_21_31_LD_RP_nn(emulZ80cpu * cpu); // Загрузить в регистровую пару значение следующих двух байт после команды
protected:
public:
	static void z80executeCMD(emulZ80cpu * cpu); // выполнить команду из таблицы команд
};

//extern emulZ80cmd commandsZ80;

../Src/z80cpu.cpp: In member function ‘void emulZ80cpu::z80run()’:
../Src/z80cpu.cpp:85:9: error: ‘z80executeCMD’ was not declared in this scope
85 | z80executeCMD(this);
| ^~~~~~~~~~~~~

А просто className::function(); не работает?

вы что-то странное создаете, не очень похожее на ООП… чешете правой рукой за левым ухом…
Зачем вам класс, если вы не собираетесь создавать даже одного экземпляра?

И хочу заметить, что довольно неудобно разбираться в том, что вы делаете, по двум заголовкам функций.
“Покажите код полностью” (с)

Так, просьба разъяснить, кто на ком стоял и кому куда надо обращаться. Кто у Вас основной и т.п.

Давайте уйдём от всех этих “основных”, а будем пользоваться именами и приводить полный код.

Я это понимал вот так, если Вы имели в виду что-то другое, объясняйте:

//
//	Это класс функций-утилит, которые имеют право обращаться
//	к private функциям класса Kaka (возможно, и други, гже он тоже
//	объявлен другом. 
//	Здесь только объявление функций этого класса,
//	определение будет отдельно
//
class Kaka;
struct Utils {
	static void util1(Kaka & k);
	static void util2(Kaka & k);
	static void util3(Kaka & k);
};

//
//	Это класс к private функциям которого зачем-то нужно 
//	обращаться посторонним функциям, которые мы собрали 
//	в классе Utils, который здесь объявлен другом
class Kaka {
	void func1(void) { Serial.println("func1 called"); }
	void func2(void) { Serial.println("func2 called"); }
	void func3(void) { Serial.println("func3 called"); }

	friend class Utils;
};


//
//	Определение функция класса Utils
//
void Utils::util1(Kaka & k) { k.func1(); }
void Utils::util2(Kaka & k) { k.func2(); }
void Utils::util3(Kaka & k) { k.func3(); }


//
// Пример использования.
//	Создаём экземпляр Kaka
//	А экземпляра Utils не создаём, статические методы так вызываем.
//
void setup(void) {
	Serial.begin(9600);
	Kaka kaka;
	Utils::util1(kaka);
	Utils::util3(kaka);
	Utils::util2(kaka);
}

void loop(void) {}
1 лайк

все заработало, надо было правильно вызывать функции, Ваши строки 44…46 меня направили на путь истинный, спасибо!

	emulZ80cmd::z80executeCMD(this);

ну да, тут можно было и без ООП, т к по факту только один экземпляр всего будет.

В любом случае - знания никогда лишними не бывают.

А моя из #10 не направила? )))

Нормально. Самый отвратительный код был написан людьми, которые пытались сделать “красиво“

Красиво то, что просто.

Видимо мне одной строки примера не хватило чтоб направить пинком в нужное русло :man_facepalming: