【问题标题】:Calling a virtual function from within an inherited function?从继承的函数中调用虚函数?
【发布时间】:2014-10-31 06:58:17
【问题描述】:

我试图在脑海中将其映射出来,但老实说,我不知道这里到底发生了什么。
到底发生了什么当我添加和删除 virtual 关键字时下面的例子?

#include <iostream>
#include <string>

class A {
public:
    A() { me = "From A"; }
    void caller() { func(); }
    virtual void func() { std::cout << me << std::endl; } // THIS LINE!
private:
    std::string me;
};

class B : public A {
public:
    B() { me = "From B"; }
    void func() { std::cout << me << std::endl; }
private:
    std::string me;
};

int main() {
    A a;
    a.caller();
    B b;
    b.caller();
    return 0;
}

使用 virtual 关键字,它会打印“From A”,然后是“From B”。
如果没有 virtual 关键字,它会打印“From A”,然后是“From A”。

到目前为止,这是我唯一一次在不涉及指针的情况下发现虚函数的用途。我认为如果删除了 virtual 关键字,编译器将执行标准操作,即重载继承的函数并最终打印“From A”和“From B”。

我认为这不仅仅是 VTable,它更多的是关于它在特定情况下的行为方式。 B 甚至有 VTable 吗?

【问题讨论】:

  • caller 本质上调用this-&gt;func,其中thisA*。这会调用动态调度 IFF A::func 是虚拟的。
  • @dyp,值得提升为答案。

标签: c++ inheritance polymorphism virtual-functions


【解决方案1】:

在您的示例中,您不会看到差异:

  • 使用虚函数,编译器将通过 VTable 生成调用,并且在运行时,每个对象都会为其真实类调用正确的函数。

  • 使用非虚函数,编译器在编译时根据对象定义的类确定要调用的正确函数。

现在尝试以下操作,以查看实际的虚函数:

A  *pa = &b;       // pointer to an A: valid as b is a B wich is also an A.  
pa -> caller();    // guess what will be called if virtual or not. 

不需要指针来试验虚函数。您也可以通过引用观察到相同的效果:

A& ra = b;       // create a reference to an A, but could as well be a parameter passed by reference.
ra.caller(); 

虚函数对多态很有用。这个想法是你使用一个类的一般对象,但在编译时你不知道在运行时该对象是否真的属于这个类,或者它是否不是一个更专业的对象(继承自班级)。

【讨论】:

  • 这没有回答 OP 的问题。
  • 我明白这一点。令人困惑的部分是虚函数已经在没有它的情况下运行。
  • 我只回答了bolt中的问题。我没有故意在 VTable 上回答,因为这些在标准的规范部分中没有定义。虚表只是意味着编译器通常会实现虚函数。
【解决方案2】:

电话

func()

等价于

this->func()

所以这里一个指针。

不过,不需要使用指针来理解行为。

甚至直接调用例如当func 在静态已知类型中是虚拟的时,b.func() 必须工作就好像它是一个虚拟调用。编译器可以基于知道b 的最衍生类型来优化它。但这是另一种考虑(优化几乎可以做任何事情)。

【讨论】:

  • 对我来说,我确实需要指针来理解行为。主要是因为当使用没有指向派生类对象等的指针的更简单示例时,使用 virtual 关键字似乎没有任何区别。即使你已经告诉我处理方式的不同。
  • 我认为这意味着B,因为它继承了使用虚拟的A,也有一个VTable,就像A一样。当你从b调用“caller()”时,它没有使用 VTable。然而,当“caller()”调用“func()”时,它必须使用 VTable 来解析使用哪个“func()”。当我说它通过调用链向上直到到达 b 来确定要使用哪个 VTable 时,我说得对吗?
  • Vtables 是 C++ 标准不强制要求的优化,但所有 C++ 编译器都使用。它们使得没有必要沿着继承链向上走。没错,caller 的调用是非虚拟的,而 func 的调用是虚拟的。为避免基类成员函数从虚拟更改为非虚拟时或如果发生意外,您可以使用关键字override 在派生类中进行覆盖。只有当它实际上是一个覆盖时才会编译。
【解决方案3】:

除了 virtual dispatch 的问题,可能会带来额外混乱的是,你有两个mes,一个声明在A,另一个声明在B。这是两个不同的对象。

B 类型的对象有两个std::string 类型的数据成员;一个独立的,一个合并到A 类型的子对象中。但是,后一个在 B 类型的方法中不能立即使用,因为它的名称被此类中引入的新 me 所掩盖(尽管您可以使用限定名称 A::me 来引用它) .

因此,尽管A::funcB::func 的主体看起来相同,但它们使用的标识符me 指的是不同的成员。

【讨论】:

  • @dyp:不,是private
  • 你说得很好。稍后我将编辑变量名称,以防止那些碰巧偶然发现的人混淆。
  • 哈,尽管如此,仍然可能令人困惑。我的意见是正确的!
  • @Cheersandhth.-Alf,私有或非私有,在 C++ 中(与 C# 不同)名称解析在之前访问检查。
  • @Cheersandhth.-Alf:听起来你在反对某事。 wtf就是这样。 :-) 成员的不可访问性与未找到其名称是另一个问题。
猜你喜欢
  • 1970-01-01
  • 2013-07-24
  • 1970-01-01
  • 2019-06-27
  • 1970-01-01
  • 2018-10-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多