通过此指针调用受保护的基类方法,该指针投射到派生类(C++)中的基类

Calling protected base class method via this pointer casted to base class in derived class (C++)

本文关键字:指针 基类 派生 C++ 调用 受保护 类方法      更新时间:2023-10-16

首先,我知道C++标准(ISO/IEC 14882:2003):第11.5节第1段,但事实并非如此(但编译器显然不这么认为)。

我试图通过这个指针调用派生类方法中的受保护基类方法,该指针静态转换为基类指针,并在MSVC2008中出现错误C2248:"A::f":无法访问类"A"中声明的受保护成员。

在"奇怪的重复模板模式"的上下文中这样做,但我可以在更简单的代码中重现这个错误,如下所示:

class B
{
protected:
    void f(){}
};
class D : public B
{
public:
    void g()
    {
        f(); // ok
        this->f(); // ok
        static_cast<B*>(this)->f(); // C2248 in MSVC2008
        dynamic_cast<B*>(this)->f(); // C2248
        ((B*)this)->f(); // C2248
    }
};
D d; d.g();

编译器似乎认为这个指针是指向其他实例的指针,是吗?

在这种情况下编译器是错误的,你认为呢?


好的,我的真实代码更像这样:

template<class T>
class B
{
public:
    void g()
    {
        f(); // error C3861: 'f': identifier not found
        this->f(); // error C3861: 'f': identifier not found
        // static_cast to derived class
        static_cast<T*>(this)->f(); // C2248 in MSVC2008
    }
};
class D : public B<D>
{
protected:
    void f(){}
};

我将this强制转换为派生的类,并且我不能使用this->f()


顺便说一句,我看到这段代码对于class E : public B<D> {...};:compilable这样的用法是不安全的,但static_cast会进行错误的强制转换。

编译器是正确的。要显式访问B::f成员函数,可以编写:

this->B::f();

相关语言为:

c++11

11.4受保护的成员访问[class.Protected]

[…]由于引用发生在某个成员的朋友或成员中,因此授予对受保护成员的访问权限class C.[…]访问受保护成员[…]涉及[s]一个(可能是隐式的)对象表达式(5.2.5)。在这种情况下,对象表达式的类应为C或从C派生的类。

因此,通过对基类B的强制转换的受保护成员访问违反了该授权,并且被禁止。由于您可以如上所述使用this->B::f(),因此也没有必要这样做。


在您实际的CRTP动机的情况下,您不能在没有static_cast的情况下调用f(),这是正确的,因为D不是B<D>的基类(继承关系是相反的)。由于D不是B<D>的基类,因此无论如何都不能从B<D>调用其protected方法。一个简单的解决方法是friend B<D>D,并在this指针上使用static_cast

template<typename T>
class B {
public:
    void g() {
        static_cast<T *>(this)->f();
    }
};
class D : public B<D>
{
    friend class B<D>;
    ...

如果让B访问Dprivate部分让您担心,您可以将private部分移动到另一个基类,并隔离D:中的CRTP机制

template<class T> class B {
public:
    void g() {
        static_cast<T*>(this)->f();
    }
};
class C {
private:
    void h();
protected:
    void f(){ std::cout << "D::fn"; }
};
class D: protected C, public B<D>
{
    friend class B<D>;
};

这里CCD_ 23被阻止调用CCD_ 24,因为友谊既不是继承的也不是传递的。

我认为编译器是对的。

假设如下:

void g()
{
    B *b1 = this;
    B *b2 = GetUnrelatedB();
    b1->f(); //Error?
    b2->f(); //Error!
}

b1的情况相当于您的static_cast,但非常奇怪的是,b1将被允许,而b2将不被允许。

引用你的第11.5段:

[…]访问必须通过指向派生类本身的指针、引用或对象。

但是static_cast<B*>(this)的类型是B*,而不是D*,不管对象本身是相同的。实际上,指针的值与这个问题无关,只是表达式的类型:

void g()
{
    B *b2 = GetUnrelatedB();
    static_cast<D*>(b2)->f(); //ok!
}

但是,一旦在this上应用了static_cast,编译器如何知道您在从B派生的类中?在我(谦虚地)看来,如果我创建了一个B对象,我希望不允许在B对象上调用私有或受保护的B方法,因为我们不想违反封装。B对象在哪里创建并不重要,只要它在B类方法之外即可。