Есть ли в 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?

3 ответа

Решение

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

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

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

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

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

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

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

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

#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 ошибки в моей первоначальной реализации (уже). Обращаться с осторожностью.

Как насчет использования множественного наследования:

class CNested {
public: 
    virtual void operator=(int i) { SomeMethod(i); }
    virtual void SomeMethod(int i) = 0;
};

class CEnclosing: public CSomeGeneric, public CNested {
    int nEncMember;
public:
    CNested& nested;
    CEnclosing() : nested(*this), nEncMember(456) {}
    virtual void SomeMethod(int i) { std:cout << i + nEncMember; }
};

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

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

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

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

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


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

Другие вопросы по тегам