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

0

То, что мне нужно, может быть сделано путем сохранения 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*) байтов памяти для каждого вложенного класса-члена. Exist эффективный и переносимый способ сделать это без необходимости хранить указатель на экземпляр CEnclosing в m_e?

  • 1
    Я думаю, что хранение указателя является популярным подходом. Память, кажется, не так уж много накладных расходов. Вы также можете отправить указатель в качестве аргумента.
  • 2
    Если CEnclosing является CEnclosing стандартной компоновки (=> нет виртуальных функций), вы можете использовать offsetof чтобы получить смещение члена, nested в объект типа CEnclosing . Я бы рекомендовал сделать CNested частным вложенным классом или как-то запретить создание экземпляров не внутри объекта CEnclosing если вы хотите использовать offsetof (также static_assert для std::is_standard_layout ).
Показать ещё 3 комментария
Теги:
c++11
offsetof
nested-class

3 ответа

2
Лучший ответ

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

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

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

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

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

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

  • 1
    Лучше использовать offsetof .
  • 1
    Я согласен, но стандарт говорит, что он не определен, если это не класс стандартной компоновки, поэтому вы рискуете с помощью диагностики компилятора.
Показать ещё 2 комментария
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

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

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

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

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

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


EDIT: Как сказал Стефан Роллан, это опасное решение, и, честно говоря, я бы не использовал его, но это единственный способ (или трюк), который я мог бы придумать для доступа к закрывающему классу из вложенного класса. Лично я, вероятно, попытаюсь пересмотреть связь между этими двумя классами, если я действительно хочу оптимизировать использование памяти до уровня, о котором вы говорили.

  • 3
    Если есть наследование (CEnclosing является подклассом), также есть вероятность, что есть смещение из-за объекта базового класса в памяти. Ваше решение очень опасно . Я бы не рекомендовал это.
  • 0
    Я согласен с вами, это «трюк», как я уже сказал в своем ответе, и я не думаю, что он гарантированно будет работать на всех компиляторах (хотя я пробовал похожие решения в VC ++, и они работают), но это Единственная хитрость, о которой я мог подумать, чтобы ответить на вопрос ТАК. Позвольте мне уточнить это в моем ответе.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню