从Template参数派生的类没有受保护的成员访问权

时间:2019-05-10 14:46:20

标签: c++ templates inheritance

从模板参数派生类时,即使派生类从std::is_base_of返回true,派生类也没有受保护的成员访问权限。

例如:

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不会发生这种情况,类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 个答案:

答案 0 :(得分:3)

对于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))

是非法的:cBaseClass(在您的使用中,从&b实例化),您无法将其强制转换为C<BaseClass>*

答案 1 :(得分:3)

派生与组成之间存在混淆:派生类基类,组成类具有作为成员。在这里,您的类既是派生的又是组成的,类c的{​​{1}}是C<A>,但也有一个指向{{1 }}类A

调用b时,将调用B的方法c.Do1,在您的示例中为Do(由于BaseClass),并且您致电A

调用C<A>时,将调用A::Do的方法c.Do2,该方法受保护!

调用Do时,将调用b的方法c.Do3,该方法是指向Do的指针,因此调用m_self

我认为您应该只删除两个成员bB::Do,您的构造函数将为:

m_Base

那么您将拥有:

m_Self

您还可以在C: BaseClass() {} 中覆盖C<A> c; c.Do(); // calls A::Do C<B> d; d.Do(); // calls B::Do

答案 2 :(得分:-1)

您不需要存储BaseClass的指针。 C无法访问m_base,因为Do()受保护,这意味着外部是私有的。

您可以致电

BaseClass::Do()

因为在这种情况下是公共的(您正在调用父类的方法)。

除了存储指针外,您的方法还用于静态多态和基于策略的设计。

您可以在这篇文章中找到更多信息: C++: Inherit class from template parameter

您想看一下 Curiously recurring template pattern