为什么可以从基类访问派生类的私人虚拟成员功能

发布于 2025-01-20 06:09:17 字数 1142 浏览 2 评论 0原文

考虑以下代码片段:

#include <iostream>
    
class Base {
public:
  Base() {
      std::cout << "Base::constr" << std::endl;
      print();
  }
  virtual ~Base() = default;

  void print() const { printImpl(); }
  
private:
    virtual void printImpl() const {
        std::cout << "Base::printImpl" << std::endl;
    }
};

class Derived : public Base {
public:        
    Derived() {
        std::cout << "Derived::constr" << std::endl;
    }
    
private:
    void printImpl() const override {
        std::cout << "Derived::printImpl" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->print();
    delete ptr;
}

以上代码将打印以下内容:

Base::constr
Base::printImpl
Derived::constr
Derived::printImpl

但是我不明白printimpl private private 函数可以从基本的print 功能。在我的理解中,指针隐式传递给print函数保留了派生的对象的地址,但是我认为私有成员函数只能从成员函数(以及friend函数)的同一类,在这里,base类与derived的类不同,尽管有一个代码>关系。

Consider the following snippet of code:

#include <iostream>
    
class Base {
public:
  Base() {
      std::cout << "Base::constr" << std::endl;
      print();
  }
  virtual ~Base() = default;

  void print() const { printImpl(); }
  
private:
    virtual void printImpl() const {
        std::cout << "Base::printImpl" << std::endl;
    }
};

class Derived : public Base {
public:        
    Derived() {
        std::cout << "Derived::constr" << std::endl;
    }
    
private:
    void printImpl() const override {
        std::cout << "Derived::printImpl" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->print();
    delete ptr;
}

The above code will print the following:

Base::constr
Base::printImpl
Derived::constr
Derived::printImpl

but I don't understand why printImpl private function is accessible from the base's print function. In my understanding this pointer implicitly passed to the print function holds the derived object's address, but I thought private member functions could be called ONLY from the member functions (and from friend functions) of the same class and here, Base class is not the same class as Derived, although there is an is a relationship.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

抚笙 2025-01-27 06:09:17

首先,正如 @Eljay 所指出的 - printImpl() 是 Base 类的一个方法,尽管是虚拟的。因此,可以从基类访问它。 Derived 只是提供了它的不同实现。虚函数的全部意义在于,您可以使用基类引用或指针来调用子类的重写。

换句话说,private只考虑子类的访问;从类的基类中保留一些私有是没有意义的:如果一个方法对于基类来说是已知的,那么它一定是基类的方法。 . 虚拟方法。

话虽如此 - 请注意 printImpl()Derived 版本实际上无法从 print() 访问 -当它在基类构造函数中调用时。这是因为在调用过程中,构造的 vtable 只是 Base 的 vtable,因此 printImpl 指向 Base::printImpl

我认为私有成员函数只能从同一类的成员函数中调用

事实上,print()Base的成员,它调用printImpl( ) - Base 的另一种方法。

First, as @Eljay notes - printImpl() is a method, albeit virtual, of the Base class. So, it's accessible from the base class. Derived merely provides a different implementation of it. And the whole point of virtual functions is that you can call a subclass' override using a base class reference or pointer.

In other words, private only regards access by subclasses; it's meaningless to keep something private from a class' base class: If a method is at all known to the base class, it must be a method of the base class... a virtual method.

Having said all that - note that the Derived version of printImpl() is effectively inaccessible from print() - when it's invoked within the base class constructor. This is because during that call, the constructed vtable is only that of Base, so printImpl points to Base::printImpl.

I thought private member functions could be called ONLY from the member functions of the same class

And indeed, print() is a member of Base, which invokes printImpl() - another method of Base.

魂归处 2025-01-27 06:09:17

基类的私有功能在派生类中无法访问,但可以过度延长,因为这两个是独立的概念。

对于打印base class的构造函数,有以下规则

在构造函数中,虚拟呼叫机制被禁用,因为尚未发生从派生类覆盖。对象是从“衍生之前的基础”构造的。

PS通常可以避免使用构造函数或破坏者的虚拟函数的通话

The private function of base class is not accessible in the derived class, but it can be overriden, since these two are separate concepts.

For the print call from the constructor of the Base class, there is a following rule:

In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn’t yet happened. Objects are constructed from the base up, “base before derived”.

P.S. In general calls to virtual functions from constructors or destructors better be avoided

ˇ宁静的妩媚 2025-01-27 06:09:17

情况 1

这里我们考虑以下语句:

Base* ptr = new Derived();

这些是上述语句的效果:

步骤 1) 使用 默认值在堆上创建 Derived 类型的对象构造函数 Derived::Derived()

第 2 步) 在进入默认构造函数 Derived::Derived() 的主体之前,编译器隐式调用默认构造函数 Base::Base()代码>.这样我们就得到了输出:

Base::constr

Step 3) 接下来,遇到 Base::Base() 中的 print() 语句。该语句相当于这样写:

this->print();

因此,Base 类的 print() 函数被调用。

第4步) 在Baseprint成员函数中,遇到调用语句printImpl();。这个语句相当于写:

this->printImpl();

但注意,目前,Base 类型的子对象的构造正在进行中。这意味着整个对象的 Derived 部分尚未构造。因此,即使 printImpl 是一个虚拟成员函数并且调用是通过指针进行的,但此时虚拟机制已被禁用。因此,this->printImpl() 调用 Base::printImpl 版本,而不是 printImpl 的派生版本。发生这种情况是因为派生类尚未初始化。这样我们就得到了输出:

Base::printImpl

第 5 步)最后,默认构造函数 Derived::Derived() 的主体被执行。因此我们得到输出:

Derived::constr

第 6 步)指针 ptr 指向新创建的派生对象的 Base 部分。


案例 2

这里我们考虑以下陈述:

 ptr->print();

现在,来自此 pionter 的文档

当在允许使用 this 关键字的任何上下文(非静态成员函数体、成员初始值设定项列表、默认成员初始值设定项)中使用非静态类成员时,隐式 this- > 自动添加在名称之前,从而生成成员访问表达式(如果成员是虚拟成员函数,则生成虚拟函数调用)。

所以,当你写:

ptr->print();  //this is equivalent to writing (*ptr).print();

上面的语句相当于写:

(*ptr).print();

这意味着指针 ptr 指向的对象的地址(只不过是 ptr 本身)隐式传递print 成员函数的隐式 this 参数。

现在,在 print 成员函数内部,包含对 printImpl() 的调用的语句相当于:

this->printImpl();

根据 在我的回答开头引用了声明。从同一个引用语句中,由于成员 printImpl 是一个虚拟成员函数,因此表达式 this->printImpl() 结果是虚拟函数调用,这意味着它会导致调用派生类printImpl函数。因此我们得到输出:

Derived::printImpl

Case 1

Here we consider the statement:

Base* ptr = new Derived();

These are the effects of the above statement:

Step 1) An object of type Derived is created on the heap using the default ctor Derived::Derived().

Step 2) Before entering the body of the default ctor Derived::Derived(), the compiler imlicitly calls the default ctor Base::Base(). Thus we get the output:

Base::constr

Step 3) Next, the statement print() inside Base::Base() is encountered. This statement is equivalent to writing:

this->print();

Hence, the print() function of class Base is called.

Step 4) Inside the print member function of Base, the call statementprintImpl(); is encountered. This statement is equivalent to writing:

this->printImpl();

But note that currently, the construction of the subobject of type Base is going on. Meaning the Derived part of the whole object has not yet been constructed. So even though the printImpl is a virtual member function and the call is made through a pointer, the virtual mechanism is disabled at this point. And thus the this->printImpl() calls the Base::printImpl version instead of the derived version of printImpl. This happened because the derived class has not yet been initialized. Thus we get the output:

Base::printImpl

Step 5) Finally, the body of the default ctor Derived::Derived() is executed. And hence we get the output:

Derived::constr

Step 6) The pointer ptr points to the Base part of the newly created derived object.


Case 2

Here we consider the statement:

 ptr->print();

Now, from this pionter's documentation:

When a non-static class member is used in any of the contexts where the this keyword is allowed (non-static member function bodies, member initializer lists, default member initializers), the implicit this-> is automatically added before the name, resulting in a member access expression (which, if the member is a virtual member function, results in a virtual function call).

So, when you wrote:

ptr->print();  //this is equivalent to writing (*ptr).print();

The above statement is equivalent to writing:

(*ptr).print();

This means that the address of the object to which the pointer ptr points(which is nothing but ptr itself) is implicitly passed to the implicit this parameter of the print member function.

Now, inside the print member function, the statement containing the call to printImpl() is equivalent to:

this->printImpl();

according to the quoted statement at the beginning of my answer. And from the same quoted statement, since the member printImpl is a virtual member function, the expression this->printImpl() results in a virtual function call, meaning it results in calling the derived class printImpl function. And hence we get the output:

Derived::printImpl
风透绣罗衣 2025-01-27 06:09:17

在代码中打印 Base::printImpl 的原因是它是从 Base 的构造函数调用的。派生类尚未构造,因此所有对虚函数的调用都将引用Base 的版本。

构造后,对 print 的调用通常会重定向到派生类的版本。函数是否被标记为私有、公共、受保护在这里并不重要。

The reason that in the code you have Base::printImpl printed is because it is called from constructor of Base. The derived class wasn't constructed yet, so all calls to virtual functions will be referred to Base's versions.

After construction the calls to print will akways redirect towards derived class's version. Whether functions are marked private, public, protected is inconsequential here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文