Как обмануть компилятор в предоставлении указателя на включающий класс?

Я читал статья о том, как 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, поэтому мне было интересно, был ли стандартный способ выполнить то, что делает этот хак.

2

Решение

Там нет актуальности 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), результаты не определены «.

3

Другие решения


По вопросам рекламы [email protected]