То, что мне нужно, можно сделать, сохранив 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
?
Как указывалось ранее, C ++ не предоставляет никакого способа сделать это. Вложенный класс не имеет особого способа найти включающий его класс. Решение, которое у вас уже есть, является рекомендуемым способом.
Если у вас есть продвинутый сценарий и вы готовы поддерживать непереносимый код, и если стоимость хранения дополнительного указателя достаточно важна для использования рискованного решения, то существует способ, основанный на объектной модели C ++. С некоторыми оговорками, в которые я не буду вдаваться, вы можете рассчитывать на то, что вложенные и вложенные классы размещаются в памяти в предсказуемом порядке, а также имеется фиксированное смещение между началом вложенных и вложенных классов.
Код выглядит примерно так:
CEnclosing e;
int offset = (char*)&e.nested - (char*)&e;
//... inside nested class
CEnclosing* pencl = (CEnclosing*)((char*)this - offset);
OTOH также возможно, что смещение макроса может просто сделать это для вас, но я не пробовал.
Если вы действительно хотите это сделать, прочитайте о тривиально копируемом и стандартном макете в стандарте.
Четкий и простой ответ на ваш вопрос — нет, C ++ 11 не имеет никаких специальных функций для обработки вашего сценария. Но в C ++ есть хитрость, позволяющая вам сделать это:
Если в CEnclosing не было виртуальной функции, указатель на nested
будет иметь то же значение, что и указатель на содержащий экземпляр. То есть:
(void*)&e == (void*)&e.nested
Это потому, что переменная nested
является первым в классе CEnclosing.
тем не мение, так как у вас есть виртуальная функция в классе CEnclosing, то все, что вам нужно сделать, это вычесть размер vtable из &e.nested
и вы должны иметь указатель на e
, Не забывайте правильно кастовать!
РЕДАКТИРОВАТЬКак сказал Стефан Роллан, это опасное решение, и, честно говоря, я бы его не использовал, но это единственный способ (или хитрость), о котором я мог подумать, чтобы получить доступ к включающему классу из вложенного класса. Лично я, вероятно, попытался бы изменить отношение между этими двумя классами, если я действительно хочу оптимизировать использование памяти до уровня, который вы упомянули.
Я считаю, что следующее может быть переносимым; хотя это не дурак. Конкретно это не будет работать через виртуальное наследование.
Кроме того, я хотел бы отметить, что это не безопасно, в том смысле, что он успешно скомпилируется, даже если передаваемый вами элемент не соответствует тому, с которым вы вычислили смещение:
#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 ошибки в моей первоначальной реализации (уже). Обращаться с осторожностью.