为什么"this"在为派生类对象调用时无法访问基类方法中的派生类成员

Why `this` can't access the derived class members from base class methods when called for derived class object

本文关键字:派生 类方法 访问 基类 成员 调用 this 对象 为什么      更新时间:2023-10-16

This指针在非常数成员函数中具有ClassName类型。

 class Base
 {
   public:
     void get()
     {
       //this->put(); Why can't I call back a derived class method eventhough 
                       **this** is pointing to derived class object.
     }
 };
 class derived: public Base
 {
   public:
   void put()
   {
    // do somthing.
   }
 };
 int main()
 {
  derived d;
  //d.get();
  d.put();
  return 0;
 }

如果我在两个函数中打印this指针的值,它是相同的,表明它是为派生类对象调用的。这里的this指针类型也是derived *

同样,正如我所理解的,如果你在调用它的方法时有一个pointer要对象,那么你只是指向offset,从pointerobject中的address开始,该方法存在于整个object layout中。

但是,当我在base类方法中有(derived)objectstart地址时,为什么我不能偏移到derived类方法。

我不明白为什么我不能这样做,因为上面的理解。我错过了一些非常基本的东西。

让我们想象一下你是一个编译器。现在你看到这个类:

class foo
{
    void bar() { this->xyz(); }
};

你是做什么的?您抱怨不知道xyz是什么,而且它肯定不是foo的成员函数。您不需要四处查找其他类,也不需要检查它们是否可能从foo派生并声明此函数——它只能以相反的方式工作。

另一种方法是,您必须声明希望派生类实现的方法的签名:

class foo
{
    void bar() { this->xyz(); }
    virtual void xyz() = 0;
};

突然间,这是有效的,但现在您不能再创建foo的实例了:每个具有纯虚拟方法的类都是一个抽象类。

但是,请注意,确实适用于模板:

template <typename T>
class foo
{
    void bar() { t.xyz(); }
    T t;
};

因为每个模板都是在编译时实例化的,所以你不会看上面的模板,而是看可能提供这样一个函数的foo<xyz_class>

Me:编译器编译Base::get函数时,无法看到Derived::put函数。

您:派生的::不是放在同一个文件中吗?为什么编译器看不到?

我:如果4年后有人在另一个文件中从Base派生出Derived1::puttttt,该怎么办?

你:嗯,也许我能理解。

您必须将方法put()声明为虚拟的。

class Base
{
public:
     virtual void put() = 0;
     void get()
     {
       //this->put(); Why can't I call back a derived class method eventhough 
                       **this** is pointing to derived class object.
     }
};
class derived: public Base
{
public:
    void put()
    {
    // do somthing.
    }
};
int main()
{
    derived d;
    //d.get();
    d.put();
    return 0;
}

Base不知道您只将其用作Derived对象的一部分;通常,可能有许多不同的派生类,其中只有一个或一些可能具有put()。那么Base是怎么编译的呢?

当您有一个指向Derived对象的Base指针时,可以强制转换以获得指向Derived的指针,并以这种方式调用方法:

Base* b = new Derived;
dynamic_cast<Derived*>(b)-> put();

如果需要,类实际上可以将this强制转换为派生实例ponter:

dynamic_cast<Derived*>(this)-> put();

您发布的代码将不会编译,因为编译器在为class Base编译代码时不知道derived类是什么。CCD_ 31指针是隐式传递给类的每个非静态成员函数的指针,以及该函数如何访问其实例的成员属性。在Base类中,成员函数this的类型为Base *const成员函数中,它的类型为const Base *

此外,this是一个非l值,这意味着你不能给它赋值

以下是关于这个指针的C++标准:

9.3.2该指针

在非静态(9.3)成员函数的主体中,关键字this是一个prvalue表达式,其值是为其调用函数的对象的地址。的成员函数中的类型类X是X*。如果成员函数被声明为const,则其类型为const X*,如果成员函数被声明为volatile,它的类型是volatile X*,如果成员函数被声明const volatile,其类型为const volabile X*。

现在,关于在你的问题中做你想做的事情,下面编译

#include <iostream>
class Base
{
   public:
     void get();
};

class derived: public Base
{
   public:
   void put()
   {
        std::cout << "This is a bad idea" << std::endl;
   }
};

void Base::get()
{
    //compiler knows what dervied class is
    static_cast<derived *>(this)->put();  
}

 int main()
 {
    derived d;
    d.get();
    return 0;
 }

输出为:This is a bad idea

我建议使用虚拟函数或模板来获得所需的行为。

考虑您已经在基类中编写了this->put()方法,在编译期间,编译器会检查同一类中的put()函数,因为您没有任何这样的函数,它会显示编译时错误。

这是一个很老的问题,通过使用虚拟方法和重载给出的示例,这肯定仍然是正确的方法;然而,我认为值得一提的是,对于路人来说,由于";显式对象参数";(也称为"推导这个"):

#include <concepts>
#include <iostream>
class Derived;
class Base
{
public:
    template <class Self>
        requires std::same_as<Self, Derived>
    void get(this Self& self)
    {
        self.put();
    }
    template <class Self>
    void get(this Self& self)
    {
        std::cout << "Base" << std::endl;
    }
};
class Derived: public Base
{
public:
    void put()
    {
        std::cout << "Derived" << std::endl;
    }
};
int main()
{
    Base b;
    Derived d;
    b.get();
    d.get();
    return 0;
}
// Output:
// -------
// Base
// Derived

编译器资源管理器上使用if constexpr而非requires 的版本

这对于基类实现具有与派生类型的对象(如CRTP)进行实际业务处理的情况非常有用。例如,该功能的作者之一Sy Brand在其关于该主题的开发博客中展示了如何使用显式对象参数来实现CRTP的相同结果,并可以说是更干净的实现。