Есть ли в C ++ 11 портативный и эффективный способ доступа к вложенному классу из вложенного класса?

То, что мне нужно, можно сделать, сохранив this указатель включения класса во вложенный класс, например, так:

class CEnclosing {
public:
class CNested : public CSomeGeneric {
public:
CNested(CEnclosing* e) : m_e(e) {}
virtual void operator=(int i) { m_e->SomeMethod(i); }
CEnclosing* m_e;
};

CNested nested;

CEnclosing() : nested(this) {}

virtual void SomeMethod(int i);
};int main()
{
CEnclosing e;
e.nested = 123;
return 0;
}

Это хорошо работает, но требует sizeof(void*) байт памяти больше для каждого вложенного класса члена. Существует эффективный и портативный способ сделать это без необходимости хранить указатель на экземпляр CEnclosing в m_e?

0

Решение

Как указывалось ранее, C ++ не предоставляет никакого способа сделать это. Вложенный класс не имеет особого способа найти включающий его класс. Решение, которое у вас уже есть, является рекомендуемым способом.

Если у вас есть продвинутый сценарий и вы готовы поддерживать непереносимый код, и если стоимость хранения дополнительного указателя достаточно важна для использования рискованного решения, то существует способ, основанный на объектной модели C ++. С некоторыми оговорками, в которые я не буду вдаваться, вы можете рассчитывать на то, что вложенные и вложенные классы размещаются в памяти в предсказуемом порядке, а также имеется фиксированное смещение между началом вложенных и вложенных классов.

Код выглядит примерно так:

   CEnclosing e;
int offset = (char*)&e.nested - (char*)&e;
//... inside nested class
CEnclosing* pencl = (CEnclosing*)((char*)this - offset);

OTOH также возможно, что смещение макроса может просто сделать это для вас, но я не пробовал.

Если вы действительно хотите это сделать, прочитайте о тривиально копируемом и стандартном макете в стандарте.

2

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

Четкий и простой ответ на ваш вопрос — нет, C ++ 11 не имеет никаких специальных функций для обработки вашего сценария. Но в C ++ есть хитрость, позволяющая вам сделать это:

Если в CEnclosing не было виртуальной функции, указатель на nested будет иметь то же значение, что и указатель на содержащий экземпляр. То есть:

(void*)&e == (void*)&e.nested

Это потому, что переменная nested является первым в классе CEnclosing.

тем не мение, так как у вас есть виртуальная функция в классе CEnclosing, то все, что вам нужно сделать, это вычесть размер vtable из &e.nested и вы должны иметь указатель на e, Не забывайте правильно кастовать!


РЕДАКТИРОВАТЬКак сказал Стефан Роллан, это опасное решение, и, честно говоря, я бы его не использовал, но это единственный способ (или хитрость), о котором я мог подумать, чтобы получить доступ к включающему классу из вложенного класса. Лично я, вероятно, попытался бы изменить отношение между этими двумя классами, если я действительно хочу оптимизировать использование памяти до уровня, который вы упомянули.

1

Я считаю, что следующее может быть переносимым; хотя это не дурак. Конкретно это не будет работать через виртуальное наследование.

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

#include <iostream>

template <typename C, typename T>
std::ptrdiff_t offsetof_impl(T C::* ptr) {
C c; // only works for default constructible classes
T* t = &(c.*ptr);
return reinterpret_cast<char*>(&c) - reinterpret_cast<char*>(t);
}

template <typename C, typename T, T C::* Ptr>
std::ptrdiff_t offsetof() {
static std::ptrdiff_t const Offset = offsetof_impl(Ptr);
return Offset;
}

template <typename C, typename T, T C::* Ptr>
C& get_enclosing(T& t) {
return *reinterpret_cast<C*>(reinterpret_cast<char*>(&t)
+ offsetof<C, T, Ptr>());
}

// Demo
struct E { int i; int j; };

int main() {
E e = { 3, 4 };

//
// BEWARE: get_enclosing<E, int, &E::j>(e.i); compiles ERRONEOUSLY too.
//                                   ^ != ^
//
E& ref = get_enclosing<E, int, &E::j>(e.j);

std::cout << (void const*)&e << " " << (void const*)&ref << "\n";
return 0;
}

Тем не менее, он работает на этом упрощенный пример, что позволило мне найти 2 ошибки в моей первоначальной реализации (уже). Обращаться с осторожностью.

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