【问题标题】:Dynamic binding inside constructor for virtual Function in C++C++中虚函数的构造函数内部的动态绑定
【发布时间】:2018-11-16 15:06:31
【问题描述】:

根据标准,我们知道构造函数总是在它们内部进行虚函数的早期绑定,因为它们不完全了解派生类层次结构的缺点。

在这种情况下,如果在我的基构造函数中使用了早期绑定,我已经将派生对象传递给了一个完全可以接受的基类指针(这里进行了向上转换)。如果使用早期绑定,则虚函数的选择应该基于指针的类型(这里是 Base *)而不是指针的内容(指针指向的对象,因为我们不知道确切的对象被指出)。在这种情况下,由于指针类型是 Base *,我们应该在这两种情况下都只调用 Base 类的虚函数。有人可以澄清一下吗?

我认为这里使用动态绑定而不是早期绑定。如果我的理解有误,请纠正我。

调用base的输出的第一行完全没问题

class Base
    {
        public:
        Base(){
            fun();
        }
        Base(Base *p)
        {
            p->fun();
        }
        virtual void fun()
        {
            cout<<"In Base"<<endl;
        }
    };

    class Derived : public Base
    {
        public:
        void fun()
        {
            cout<<"In Derived"<<endl;
        }
    };

    int main()
    {
        Derived d;
        Base b(&d);
    }

O/P:

In Base
In Derived

【问题讨论】:

  • “我们知道构造函数总是使用早期绑定”错误

标签: c++ constructor virtual-functions


【解决方案1】:

构造函数体内的虚拟调用规则适用于当前正在构造的对象,因为该对象尚未被视为任何派生类的对象。 (对于当前被销毁的对象也有类似的规则。)它与使用编译时类型(例如语法ClassName::member_func()forces)意义上的“早期绑定”关系不大。

您的代码有两个不同的对象db,当您到达p-&gt;fun(); 行时,d 的构造函数已经完全完成。

详细说明:

  1. 程序输入main
  2. 对象d 是使用Derived 隐式声明的默认构造函数创建的。
  3. Derived::Derived() 所做的第一件事是通过调用默认构造函数Base::Base() 创建基类子对象。
  4. Base::Base() 的主体调用fun()。由于我们还没有进入Derived::Derived()构造函数的主体,这个虚拟查找调用Base::fun()
  5. Base::Base() 完成。
  6. Derived::Derived() 的主体为空,执行并结束。
  7. 回到main,通过将指向d 的指针传递给构造函数Base::Base(Base*) 来创建对象b
  8. Base::Base(Base *p) 的主体调用p-&gt;fun()。由于p 是指向d 的指针,而d 已经是一个完全构造的Derived 类型的对象,因此此虚拟查找调用Derived::fun()

与这个略有不同的示例进行对比,我们定义了一个默认构造函数Derived 以将this(隐式转换为Base*)传递给Base 子对象的构造函数。 (这是有效的,但如果指针以其他方式使用可能会有风险,例如在 Base 的基类或成员的初始化程序中。)

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
    Base(){
        fun();
    }
    Base(Base *p)
    {
        p->fun();
    }
    virtual void fun()
    {
        cout<<"In Base"<<endl;
    }
};

class Derived
{
public:
    Derived() : Base(this) {}
    virtual void fun() override
    {
        cout << "In Derived" << endl;
    }
};

int main()
{
    Derived d;
}

这个程序将只打印“In Base”,因为现在在Base::Base(Base *p) 中,p 确实指向当前正在构造的同一个对象。

【讨论】:

  • @aschelper,很好的解释。根据您的最后一条语句,发送到基类构造函数的“this”具有派生对象或基对象的值?
  • @user2906818 两者都不是,指针是它所指向的东西的不同值。此时,它是一个无效的派生指针,可隐式转换为有效的基指针。在构造函数的主体中,它是一个有效的派生指针
  • "您描述的“早期绑定”规则适用于当前正在构建的对象"您所说的“早期绑定”是什么意思?
  • @curiousguy OP 是这么描述的,虽然我以前从未听说过这个词。
  • @aschepler 早期绑定意味着在编译时(“早期”)根据使用的表达式类型选择调用的函数;正如 OP 所说:“如果使用早期绑定,则虚函数的选择应基于指针的类型(此处为 Base *),而不是指针的内容
【解决方案2】:

原因是C++类是从基类到派生类构造的,并且在对象创建过程完成时创建完整对象的虚拟调用表。因此,在上面的代码摘录中调用了基类函数。除非另有强制要求,否则绝不应在构造函数中进行虚函数调用。以下是Bjarne Stroustrup's C++ Style and Technique FAQ的摘录:

  • 在构造函数中,虚拟调用机制被禁用,因为 从派生类覆盖还没有发生。对象是从基础开始构建的,“基础先于派生”。

  • 销毁是在“基类之前的派生类”完成的,所以是虚拟的 函数的行为就像在构造函数中一样:只使用局部定义—— 并且没有调用覆盖函数以避免触摸(现在 已销毁)对象的派生类部分。

【讨论】:

  • 非常好的解释。因此,您的意思是,当我们第一次构造派生对象时,我们进入默认的 Base 构造函数,并且由于那时派生对象尚未构造,我们进行早期绑定并调用 Base Virtual 函数。在第二种情况下,因为我们有已经形成的派生对象,所以我们在这里进行动态绑定,因此调用派生虚函数。如果我的理解有误,请纠正我
  • @user2906818 "我们进行早期绑定" 不,我们一如既往地根据对象的真实类型进行虚拟调用
  • "虚拟调用机制被禁用" no
  • @curiousguy 你检查Bjarne Stroustrup's C++ Style and Technique FAQ了吗?
  • @CanYuce 为什么我需要阅读该资源?不管怎样,我是对的,不同意我的人是错的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-26
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 2012-11-06
相关资源
最近更新 更多