Производный класс из аргумента Template не имеет защищенного доступа к члену - PullRequest
3 голосов
/ 10 мая 2019

При получении класса из аргумента шаблона производный класс не имеет защищенного доступа к члену, даже если он возвращает true из std::is_base_of

например:

class A
{
    protected:
        virtual void Do() 
        {
            std::cout << "A::Do()";
        }
};

class B : public A
{
    protected:
        virtual void Do()
        {
            std::cout << "B::Do() - "; A::Do();
        }
};

и шаблон класса

template <class BaseClass>
class C : public BaseClass
{
    public:
        C(BaseClass* c) :
            m_Base(c),
            m_Self(static_cast<C<BaseClass>*>(c))
        {}

        // this will never call the passed in class version
        void Do1()
        {
            BaseClass::Do();
        }

        // this has the error 'virtual int A::Do()' is protected within this context
        void Do2()
        {
            m_Base->Do();
        }

        void Do3()
        {
            m_Self->Do();
        }

        BaseClass* m_Base;
        C<BaseClass>* m_Self;
    };

если мы затем создадим экземпляр и вызовем

int main()
{
    A a;
    B b;
    C<A> c(&b);

    std::is_base_of<A, C<A> >::value; // this is true

    c.Do1(); // calls A::Do()
    c.Do2(); // throws a protected access error
    c.Do3(); // calls B::Do()
}

приведение указателя BaseClass к указателю типа C позволяет нам правильно вызывать функцию, так как мы можем вызывать функцию защищенного члена, и корректно вызывается переопределение.

такого рода вещи не случаются с классом B, который может вызывать A :: Do () напрямую, как это происходит от A.

C является производным от BaseClass, но не имеет доступа к защищенным функциям-членам. указатель на BaseClass действует как внешний указатель, а не вызывается классом шаблона.

Таким образом, общий вопрос:

  • это законный / безопасный / хороший код?
  • есть причина почему?
  • Есть ли лучший способ сделать это?

изменить для более подробной информации и рассуждений:

в этом случае C - это класс-оболочка, который позволяет мне перенаправлять вызовы функций экземпляра B, добавляя при этом функциональность. отсюда необходимость хранить экземпляр B, так как он содержит переменные, которые будут обновлены, а B - это конкретный экземпляр, который я храню или извлекаю из него и специализируюсь на нем.

если бы у нас было

class D : public B
{
    protected:
        virtual void Do()
        {
            std::cout << "D::Do()";
        }
};

и мы создали так

D d;
C<B> c(&d)

Я ожидал бы, когда позвоню c.Do(), что он вызовет специализацию D, и в этом конкретном случае.

как бы то ни было, похоже, мне нужно сделать C другом BaseClass следующим образом:

// forward declared as they live separately
template <class BaseType> class C;

class B
{
    ...

    friend class C<B>;
};

Ответы [ 3 ]

3 голосов
/ 10 мая 2019

Существует путаница между деривацией и составом: производный класс является базовым классом, составной класс имеет базовый класс в качестве члена. Здесь ваш класс является производным и составленным , c класса C<A> равен A, но также имеет указатель на b класса B.

Когда вы вызываете c.Do1, вы вызываете метод Do из BaseClass, который в вашем примере равен A (из-за C<A>), и вы звоните A::Do.

Когда вы вызываете c.Do2, вы вызываете метод Do из b, который защищен!

Когда вы вызываете c.Do3, вы вызываете метод Do из m_self, который является указателем на b, поэтому вы вызываете B::Do.

Я думаю, что вы должны просто удалить 2 члена m_Base и m_Self, и ваш конструктор будет:

C: BaseClass() {}

Тогда у вас будет:

C<A> c;
c.Do();  // calls A::Do
C<B> d;
d.Do();  // calls B::Do

Вы также можете переопределить Do в C.

3 голосов
/ 10 мая 2019

Это недопустимо для m_Base типа A*.

m_Base->Do();

Вы не можете получить доступ к защищенному члену объекта, кроме случаев, когда указанным объектом является *this. Это огромное упрощение, но оно работает в 99% случаев.

В качестве решения вы можете сделать template<class T> class C friend из A:

//define C beforehand

class A
{
friend template<class T> class C;
protected:
    virtual Do() 
    {
        std::cout << "A::Do()";
    }
};

Наконец, обратите внимание, что

m_Self(static_cast<C<BaseClass>*>(c))

недопустимо: c, являясь BaseClass (в вашем использовании, инстанцируется с &b), вы не можете привести его к C<BaseClass>*.

0 голосов
/ 10 мая 2019

Вам не нужно хранить указатель BaseClass.C не может получить доступ к m_base, потому что Do() защищен, что означает приватность извне.

Вы можете просто позвонить

BaseClass::Do()

, потому что в этом контексте общедоступна (выВы вызываете метод вашего родительского класса).

За исключением хранения указателей, ваш подход используется для статического полиморфизма и разработки на основе политик.

Вы можете найти больше в этом посте: C ++: наследовать класс из параметра шаблона

Вы хотели бы взглянуть на любопытно повторяющийся шаблон шаблона

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...