Использование offsetof () для получения объекта владельца из переменной-члена

Я хотел бы реализовать функцию GetParent () здесь

class ChildClass;

class ParentClass
{
public:
....
ChildClass childObj;
....
};

class ChildClass
{
friend class ParentClass;
private:
ChildClass();

public:
ParentClass* GetParent();
};

Я пытался создать закрытую переменную-член, которая хранит указатель на родительский объект.
Однако этот метод требует дополнительной памяти.

class ChildClass
{
friend class ParentClass;

private:
ChildClass();

ParentClass* m_parent;

public:
ParentClass* GetParent()
{
return m_parent;
}
};

Поэтому я использовал макрос offsetof () (затраты на выполнение вызова offsetof () можно игнорировать), но я не уверен, что этот подход безопасен. Будет ли это работать в любой ситуации? Есть ли лучшая идея?

class ChildClass
{
public:
ParentClass* GetParent()
{
return reinterpret_cast<ParentClass*>(
reinterpret_cast<int8_t*>(this) - offsetof(ParentClass, childObj)
);
}
};

0

Решение

Расчет адреса объекта контейнера с помощью offsetof является безопасный в том смысле, что это может работать. offsetof обычно используется в Си для этой цели. Смотрите, например, container_of макрос в ядре Linux.

Это может быть небезопасно в том смысле, что если есть ChildClass экземпляр, который является не эта конкретная переменная-член, тогда у вас есть неопределенное поведение в ваших руках. Конечно, поскольку конструктор является частным, вы должны быть в состоянии предотвратить это.

Еще одна причина, почему это не безопасно, это то, что это имеет неопределенное поведение если тип контейнера не является стандартный тип макета.

Таким образом, это может работать до тех пор, пока вы принимаете во внимание предостережения. Ваша реализация, однако, сломана. Второй параметр offsetof макрос должен быть именем члена. В этом случае это должно быть childObj и не e[index] который не является именем члена.

Также (возможно, кто-то исправит меня, если я ошибаюсь, но я думаю) приведение к не связанному типу uint8_t* перед выполнением арифметики с указателем, а затем приведением к другому не связанному типу кажется немного опасным. Я рекомендую использовать char* как промежуточный тип. Гарантируется, что sizeof(char) == 1 и у него есть особые исключения относительно псевдонимов и отсутствия представлений ловушек.

Возможно, стоит упомянуть, что это использование — или любое другое использование, кроме использования с массивами — арифметики указателей не определен по стандарту. Что строго говоря делает offsetof бесполезный. Тем не менее, указатели являются широко используется за пределами массивов, поэтому отсутствие стандартной поддержки в этом случае можно игнорировать.

2

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

Вот более общее решение для будущих посетителей:

#include <cstddef>
#include <type_traits>

template <class Struct, std::size_t offset, class Member>
Struct &get_parent_struct_tmpl(Member &m){
static_assert(std::is_standard_layout<Struct>::value,
"Given struct must have a standard layout type");
return *reinterpret_cast<Struct *>(reinterpret_cast<char *>(&m) - offset);
}
#define get_parent_struct(STRUCTNAME, MEMBERNAME, MEMBERREF)\
get_parent_struct_tmpl<STRUCTNAME, offsetof(STRUCTNAME, MEMBERNAME)>(MEMBERREF)

Прецедент:

#include <cassert>

struct Foo{
double d;
int i;
bool b;
char c;
bool b2;
};

int main(){
Foo f;
bool &br = f.b;

Foo &fr = get_parent_struct(Foo, b, br);

assert(&fr == &f);
}

Eсть static_assert который защищает от UB, вызванного тем, что данная структура не имеет стандарт расположение как упомянуто user2079303.

Код, как показано, требует C ++ 11, однако вы можете удалить #include <type_traits> и static_assert однако для компиляции в C ++ 03 вам нужно будет вручную убедиться, что у вас стандартный тип макета.

2

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