Я читал статья о том, как C ++ не имеет полевых методов доступа как часть языка.
В конце статьи автор дает решение на основе макросов, которое эмулирует средства доступа к полям для классов:
// a little trick to fool compiler we are not accessing NULL pointer here
#define property_offset(type, name) \
(((char*)&((type*)(0xffff))->name) - (char*)(0xffff))
#define property_parent(type, name) \
((type*)((char*)(this) - property_offset(type, name)))
// macro defining property
#define property(type, name, parent) \
struct name##_property { \
operator type() { return property_parent(parent, name)->get_##name(); } \
void operator=(type v) { property_parent(parent, name)->set_##name(v); } \
\
private: \
char zero[0]; \
} name
// our main class
class Node {
/* visitCount will act as a field accessor */
property(int, visitCount, Node);
};
Когда я запускаю это через препроцессор, я получаю:
class Node {
struct visitCount_property {
operator int() { return ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->get_visitCount(); }
void operator=(int v) { ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->set_visitCount(v); }
private: char zero[0];
} visitCount;
};
Идея в том, что я бы также добавил свои собственные реализации:
int get_visitCount();
void set_visitCount(int v);
И это будет выглядеть так, как будто visitCount
был прямой доступ.
Однако на самом деле функции будут вызываться за кулисами:
Node n;
n.visitCount = 1; //actually calls set method
cout << n.VisitCount; //actually calls get method
Я хотел бы узнать больше об этой уловке доступа к включающему классу:
((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))
Какова актуальность 0xffff
?
В десятичном виде это: 65535
,
Как это обманывает компилятор для доступа к классу, который содержит класс visitCount?
Я также вижу, что это не работает на MSVC, поэтому мне было интересно, был ли стандартный способ выполнить то, что делает этот хак.
Там нет актуальности 0xffff
, Это просто какой-то номер. Это может быть ноль (и на самом деле было бы проще, если бы это было). Давайте разберем это на части и перепишем 0xffff
быть addr
:
(((char*)&((type*)(addr))->name) - (char*)(addr))
(type*)(addr)
просто дает нам немного type*
это начинается в addr
, Это reinterpret_cast
, Итак, давайте назовем это obj
:
(((char*)&obj->name) - (char*)(addr))
Мы можем даже заменить второй addr
с obj
— это неправильный тип, но мы все равно ведем кастинг, и это добавляет ясности в происходящее:
(((char*)&obj->name) - (char*)(obj))
&obj->name
просто дает нам указатель на этот конкретный член, так что давайте назовем это mem_ptr
((char*)mem_ptr) - (char*)(obj)
Теперь понятно — мы берем адрес участника (как char*
) и вычитая адрес родительского объекта (как char*
). Мы использовали 0xffff
просто иметь одинаковый начальный адрес в обоих местах.
Обратите внимание, что стандарт C ++ также определяет макрос для этого напрямую. Это называется offsetof
. С оговоркой «Если тип не является классом стандартного макета (раздел 9), результаты не определены «.