为什么构造函数中对虚拟成员函数的调用是非虚拟调用?

发布于 2024-07-23 07:07:05 字数 408 浏览 10 评论 0原文

假设我有两个 C++ 类:

class A
{
public:
  A() { fn(); }

  virtual void fn() { _n = 1; }
  int getn() { return _n; }

protected:
  int _n;
};

class B : public A
{
public:
  B() : A() {}

  virtual void fn() { _n = 2; }
};

如果我编写以下代码:

int main()
{
  B b;
  int n = b.getn();
}

人们可能期望 n 设置为 2。

结果,n 设置为 1。为什么?

Suppose I have two C++ classes:

class A
{
public:
  A() { fn(); }

  virtual void fn() { _n = 1; }
  int getn() { return _n; }

protected:
  int _n;
};

class B : public A
{
public:
  B() : A() {}

  virtual void fn() { _n = 2; }
};

If I write the following code:

int main()
{
  B b;
  int n = b.getn();
}

One might expect that n is set to 2.

It turns out that n is set to 1. Why?

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

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

发布评论

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

评论(13

妄司 2024-07-30 07:07:06

解决您的问题的一种方法是使用工厂方法来创建对象。

  • 为包含虚拟方法 afterConstruction() 的类层次结构定义一个公共基类:
class Object
{
public:
  virtual void afterConstruction() {}
  // ...
};
  • 定义一个工厂方法:
template< class C >
C* factoryNew()
{
  C* pObject = new C();
  pObject->afterConstruction();

  return pObject;
}
  • 像这样使用它:
class MyClass : public Object 
{
public:
  virtual void afterConstruction()
  {
    // do something.
  }
  // ...
};

MyClass* pMyObject = factoryNew();

One solution to your problem is using factory methods to create your object.

  • Define a common base class for your class hierarchy containing a virtual method afterConstruction():
class Object
{
public:
  virtual void afterConstruction() {}
  // ...
};
  • Define a factory method:
template< class C >
C* factoryNew()
{
  C* pObject = new C();
  pObject->afterConstruction();

  return pObject;
}
  • Use it like this:
class MyClass : public Object 
{
public:
  virtual void afterConstruction()
  {
    // do something.
  }
  // ...
};

MyClass* pMyObject = factoryNew();

萧瑟寒风 2024-07-30 07:07:06

正如已经指出的,这些对象是在构造时自下而上创建的。 当构造基对象时,派生对象还不存在,因此虚函数重写无法工作。

但是,如果您的 getter 返回常量,或者可以用静态成员函数表示,则可以通过使用静态多态性而不是虚函数的多态 getter 来解决这个问题,此示例使用 CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)。

template<typename DerivedClass>
class Base
{
public:
    inline Base() :
    foo(DerivedClass::getFoo())
    {}

    inline int fooSq() {
        return foo * foo;
    }

    const int foo;
};

class A : public Base<A>
{
public:
    inline static int getFoo() { return 1; }
};

class B : public Base<B>
{
public:
    inline static int getFoo() { return 2; }
};

class C : public Base<C>
{
public:
    inline static int getFoo() { return 3; }
};

int main()
{
    A a;
    B b;
    C c;

    std::cout << a.fooSq() << ", " << b.fooSq() << ", " << c.fooSq() << std::endl;

    return 0;
}

通过使用静态多态性,基类知道要调用哪个类的 getter,因为信息是在编译时提供的。

As has been pointed out, the objects are created base-down upon construction. When the base object is being constructed, the derived object does not exist yet, so a virtual function override cannot work.

However, this can be solved with polymorphic getters that use static polymorphism instead of virtual functions if your getters return constants, or otherwise can be expressed in a static member function, This example uses CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern).

template<typename DerivedClass>
class Base
{
public:
    inline Base() :
    foo(DerivedClass::getFoo())
    {}

    inline int fooSq() {
        return foo * foo;
    }

    const int foo;
};

class A : public Base<A>
{
public:
    inline static int getFoo() { return 1; }
};

class B : public Base<B>
{
public:
    inline static int getFoo() { return 2; }
};

class C : public Base<C>
{
public:
    inline static int getFoo() { return 3; }
};

int main()
{
    A a;
    B b;
    C c;

    std::cout << a.fooSq() << ", " << b.fooSq() << ", " << c.fooSq() << std::endl;

    return 0;
}

With the use of static polymorphism, the base class knows which class' getter to call as the information is provided at compile-time.

咿呀咿呀哟 2024-07-30 07:07:06

C++ 标准 (ISO/IEC 14882-2014 ) 说的是:

可以调用成员函数,包括虚函数(10.3)
在建造或销毁期间(12.6.2)。 当虚函数
直接或间接从构造函数或从
析构函数,包括在构造或销毁期间
类的非静态数据成员以及调用的对象
apply 是正在构建或销毁的对象(称之为 x),
调用的函数是构造函数或中的最终重写器
析构函数的类,而不是在更派生的类中重写它的类。
如果虚函数调用使用显式类成员访问
(5.2.5) 对象表达式指的是 x 的完整对象
或该对象的基类子对象之一,但不是 x 或其之一
基类子对象,行为未定义

因此,不要从尝试调用正在构造或销毁的对象的构造函数或析构函数调用虚拟函数,因为构造顺序从基类到派生开始,并且析构函数的顺序从派生到基类开始。

因此,尝试从构造中的基类调用派生类函数是危险的。同样,对象以与构造相反的顺序被销毁,因此尝试从析构函数调用更派生类中的函数可能会访问已经存在的资源。已被释放。

The C++ Standard (ISO/IEC 14882-2014) say's:

Member functions, including virtual functions (10.3), can be called
during construction or destruction (12.6.2). When a virtual function
is called directly or indirectly from a constructor or from a
destructor, including during the construction or destruction of the
class’s non-static data members, and the object to which the call
applies is the object (call it x) under construction or destruction,
the function called is the final overrider in the constructor’s or
destructor’s class and not one overriding it in a more-derived class.
If the virtual function call uses an explicit class member access
(5.2.5) and the object expression refers to the complete object of x
or one of that object’s base class subobjects but not x or one of its
base class subobjects, the behavior is undefined.

So, Don't invoke virtual functions from constructors or destructors that attempts to call into the object under construction or destruction, Because the order of construction starts from base to derived and the order of destructors starts from derived to base class.

So, attempting to call a derived class function from a base class under construction is dangerous.Similarly, an object is destroyed in reverse order from construction, so attempting to call a function in a more derived class from a destructor may access resources that have already been released.

慕烟庭风 2024-07-30 07:07:06

你知道 Windows 资源管理器的崩溃错误吗?! “纯虚函数调用...”

同样的问题...

class AbstractClass 
{
public:
    AbstractClass( ){
        //if you call pureVitualFunction I will crash...
    }
    virtual void pureVitualFunction() = 0;
};

因为没有实现函数 pureVitualFunction() 并且在构造函数中调用该函数,所以程序将崩溃。

Do you know the crash error from Windows explorer?! "Pure virtual function call ..."

Same problem ...

class AbstractClass 
{
public:
    AbstractClass( ){
        //if you call pureVitualFunction I will crash...
    }
    virtual void pureVitualFunction() = 0;
};

Because there is no implemetation for the function pureVitualFunction() and the function is called in the constructor the program will crash.

破晓 2024-07-30 07:07:06

A::A() 的作用是:

  1. 设置 vptr(A 的隐藏数据成员),使其指向 vtable
    请注意,vptr 是每个对象,而 vtable 是每个类,
  2. 运行您在 A::A() 主体中显式编写的代码。

B::B() 的作用是:

  1. 调用 A::A()
  2. 修改上述 vptr(不是任何 vtable),使其指向
    B 的 vtable,不再是 A 的 vtable。
  3. 运行您在 B::B() 主体中显式编写的代码

因此,A::A() 保证调用 A::fn() 而不是 B::fn()。

注释

  • 在您的情况下, A::A() 直接调用 fn() (而不是调用另一个函数,后者又调用 fn),因此在编译时编译器肯定知道 A:: fn() 应该由 A 的构造函数调用,因此可能会选择早期绑定来从 A::A() 调用 fn(),而不是使用 vptr 和 vtable。
  • 在多重继承的情况下,派生类的构造函数
    可能需要修改多个 vptr。

What A::A() does is:

  1. set the vptr (a hidden data member of A), making it point to vtable
    of A. Note that vptr is per object, while vtable is per class
  2. run the code you wrote explicitly in the A::A() body.

What B::B() does is:

  1. call A::A()
  2. modify the above-mentioned vptr (not any vtable), making it point to
    vtable of B, not to vtable of A any more.
  3. run the code you wrote explicitly in the B::B() body

Therefore, A::A() is guaranteed to call A::fn() rather than B::fn().

Notes

  • In your case, A::A() calls fn() directly (instead of calling another function, which in turn calls fn), so during compile time the compiler knows for sure that A::fn() should be called by A's constructor, and thus will probably choose early-binding for the call to fn() from A::A(), rather than use the vptr and vtable.
  • In the case of multiple inheritance, the derived class constructor
    might need to modify more than one vptr.
岁月静好 2024-07-30 07:07:06

vtable 由编译器创建。
类对象有一个指向其 vtable 的指针。 当它开始生命时,该vtable指针指向vtable
的基类。 在构造函数代码的末尾,编译器生成代码来重新指向vtable指针
到该类的实际 vtable。 这确保调用虚函数的构造函数代码调用
这些函数的基类实现,而不是类中的重写。

The vtables are created by the compiler.
A class object has a pointer to its vtable. When it starts life, that vtable pointer points to the vtable
of the base class. At the end of the constructor code, the compiler generates code to re-point the vtable pointer
to the actual vtable for the class. This ensures that constructor code that calls virtual functions calls the
base class implementations of those functions, not the override in the class.

难理解 2024-07-30 07:07:06

作为补充,调用尚未完成构造的对象的虚函数也会面临同样的问题。

例如,在一个对象的构造函数中启动一个新线程,并将该对象传递给新线程,如果新线程在该对象构造完成之前调用该对象的虚函数,将会导致意外的结果。

例如:

#include <thread>
#include <string>
#include <iostream>
#include <chrono>

class Base
{
public:
  Base()
  {
    std::thread worker([this] {
      // This will print "Base" rather than "Sub".
      this->Print();
    });
    worker.detach();
    // Try comment out this code to see different output.
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  virtual void Print()
  {
    std::cout << "Base" << std::endl;
  }
};

class Sub : public Base
{
public:
  void Print() override
  {
    std::cout << "Sub" << std::endl;
  }
};

int main()
{
  Sub sub;
  sub.Print();
  getchar();
  return 0;
}

这将输出:

Base
Sub

As a supplement, calling a virtual function of an object that has not yet completed construction will face the same problem.

For example, start a new thread in the constructor of an object, and pass the object to the new thread, if the new thread calling the virtual function of that object before the object completed construction will cause unexpected result.

For example:

#include <thread>
#include <string>
#include <iostream>
#include <chrono>

class Base
{
public:
  Base()
  {
    std::thread worker([this] {
      // This will print "Base" rather than "Sub".
      this->Print();
    });
    worker.detach();
    // Try comment out this code to see different output.
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  virtual void Print()
  {
    std::cout << "Base" << std::endl;
  }
};

class Sub : public Base
{
public:
  void Print() override
  {
    std::cout << "Sub" << std::endl;
  }
};

int main()
{
  Sub sub;
  sub.Print();
  getchar();
  return 0;
}

This will output:

Base
Sub
帅气称霸 2024-07-30 07:07:06

为了回答运行该代码时会发生什么/为什么,我通过编译它
g++ -ggdb main.cc,并使用 gdb 逐步执行。

main.cc:

class A { 
  public:
    A() {
      fn();
    }
    virtual void fn() { _n=1; }
    int getn() { return _n; }

  protected:
    int _n;
};


class B: public A {
  public:
    B() {
      // fn();
    }
    void fn() override {
      _n = 2;
    }
};


int main() {
  B b;
}

main 处设置断点,然后单步执行 B(),打印 this ptr,单步执行 A()(基本构造函数):

(gdb) step
B::B (this=0x7fffffffde80) at main2.cc:16
16    B() {
(gdb) p this
$27 = (B * const) 0x7fffffffde80
(gdb) p *this
$28 = {<A> = {_vptr.A = 0x7fffffffdf80, _n = 0}, <No data fields>}
(gdb) s
A::A (this=0x7fffffffde80) at main2.cc:3
3     A() {
(gdb) p this
$29 = (A * const) 0x7fffffffde80

显示this 最初指向在 0x7fffffffde80 处的堆栈上构造的派生 B obj b。 下一步是进入基 A() 构造函数,并且 this 变为相同地址的 A * const,这是有意义的,因为基 A 正好位于 B 的开头目的。 但它仍然没有被构造:

(gdb) p *this
$30 = {_vptr.A = 0x7fffffffdf80, _n = 0}

再一步:

(gdb) s
4       fn();
(gdb) p *this
$31 = {_vptr.A = 0x402038 <vtable for A+16>, _n = 0}

_n已经被初始化,并且它的虚函数表指针包含virtual void A::fn()的地址:

(gdb) p fn
$32 = {void (A * const)} 0x40114a <A::fn()>
(gdb) x/1a 0x402038
0x402038 <_ZTV1A+16>:   0x40114a <_ZN1A2fnEv>

所以这是完全有道理的下一步在给定活动 this_vptr.A 的情况下,通过 this->fn() 执行 A::fn()。 又一步,我们回到 B() 构造函数:

(gdb) s
B::B (this=0x7fffffffde80) at main2.cc:18
18    }
(gdb) p this
$34 = (B * const) 0x7fffffffde80
(gdb) p *this
$35 = {<A> = {_vptr.A = 0x402020 <vtable for B+16>, _n = 1}, <No data     fields>}

基础 A 已经构造完毕。 请注意,存储在虚拟函数表指针中的地址已更改为派生类 B 的 vtable。因此,对 fn() 的调用将通过 this->fn() 选择派生类重写 B::fn(),给定活动 this_vptr.A (在 B() 中取消对 B::fn() 调用的注释以查看此内容。)再次检查 _vptr.A 中存储的 1 个地址显示它现在指向派生类重写:

(gdb) p fn
$36 = {void (B * const)} 0x401188 <B::fn()>
(gdb) x/1a 0x402020
0x402020 <_ZTV1B+16>:   0x401188 <_ZN1B2fnEv>

通过查看此示例,并通过查看具有 3 级继承的示例,可以看出,当编译器向下构造基本子对象时, this* 的类型 和 _vptr.A 中的相应地址发生变化以反映当前正在构造的子对象, - 因此它会指向最派生的类型。 因此,我们希望从 ctor 内部调用的虚拟函数能够选择该级别的函数,即,结果与非虚拟相同。对于 dtor 也是如此,但相反。 在构造成员时,this 成为成员的 ptr,因此它们也可以正确调用为它们定义的任何虚拟函数

To answer what happens/why when you run that code, I compiled it via
g++ -ggdb main.cc, and stepped through with gdb.

main.cc:

class A { 
  public:
    A() {
      fn();
    }
    virtual void fn() { _n=1; }
    int getn() { return _n; }

  protected:
    int _n;
};


class B: public A {
  public:
    B() {
      // fn();
    }
    void fn() override {
      _n = 2;
    }
};


int main() {
  B b;
}

Setting a break point at main, then stepping into B(), printing the this ptr, taking a step into A() (base constructor):

(gdb) step
B::B (this=0x7fffffffde80) at main2.cc:16
16    B() {
(gdb) p this
$27 = (B * const) 0x7fffffffde80
(gdb) p *this
$28 = {<A> = {_vptr.A = 0x7fffffffdf80, _n = 0}, <No data fields>}
(gdb) s
A::A (this=0x7fffffffde80) at main2.cc:3
3     A() {
(gdb) p this
$29 = (A * const) 0x7fffffffde80

shows that this initially points at the derived B obj b being constructed on the stack at 0x7fffffffde80. The next step is into the base A() ctor and this becomes A * const to the same address, which makes sense as the base A is right in the start of B object. but it still hasn't been constructed:

(gdb) p *this
$30 = {_vptr.A = 0x7fffffffdf80, _n = 0}

One more step:

(gdb) s
4       fn();
(gdb) p *this
$31 = {_vptr.A = 0x402038 <vtable for A+16>, _n = 0}

_n has been initialized, and it's virtual function table pointer contains the address of virtual void A::fn():

(gdb) p fn
$32 = {void (A * const)} 0x40114a <A::fn()>
(gdb) x/1a 0x402038
0x402038 <_ZTV1A+16>:   0x40114a <_ZN1A2fnEv>

So it makes perfect sense that the next step executes A::fn() via this->fn() given the active this and _vptr.A. Another step and we're back in B() ctor:

(gdb) s
B::B (this=0x7fffffffde80) at main2.cc:18
18    }
(gdb) p this
$34 = (B * const) 0x7fffffffde80
(gdb) p *this
$35 = {<A> = {_vptr.A = 0x402020 <vtable for B+16>, _n = 1}, <No data     fields>}

The base A has been constructed. Note that address stored in the virtual function table pointer has changed to the vtable for derived class B. And so a call to fn() would select the derived class override B::fn() via this->fn() given the active this and _vptr.A (un-comment call to B::fn() in B() to see this.) Again examining 1 address stored in _vptr.A shows it now points to the derived class override:

(gdb) p fn
$36 = {void (B * const)} 0x401188 <B::fn()>
(gdb) x/1a 0x402020
0x402020 <_ZTV1B+16>:   0x401188 <_ZN1B2fnEv>

By looking at this example, and by looking at one with a 3 level inheritance, it appears that as the compiler descends to construct the base sub-objects, the type of this* and the corresponding address in _vptr.A change to reflect the current sub-object being constructed, - so it gets left pointing to the most derived type's. So we would expect virtual functions called from within ctors to choose the function for that level, i.e., same result as if they were non-virtual.. Likewise for dtors but in reverse. And this becomes a ptr to member while members are being constructed so they also properly call any virtual functions that are defined for them.

小巷里的女流氓 2024-07-30 07:07:06

我刚刚在程序中遇到了这个错误。
我有这样的想法:如果该方法在构造函数中被标记为纯虚拟会发生什么?

class Base {
public:
    virtual int getInt() = 0;
    
    Base(){
        printf("int=%d\n", getInt());
    }
};

class Derived : public Base {
    public:
        virtual int getInt() override {return 1;}
};

还有……有趣的事情! 您首先会收到编译器的警告:

warning: pure virtual ‘virtual int Base::getInt() const’ called from constructor

以及来自 ld 的错误!

/usr/bin/ld: /tmp/ccsaJnuH.o: in function `Base::Base()':
main.cpp:(.text._ZN4BaseC2Ev[_ZN4BaseC5Ev]+0x26): undefined reference to `Base::getInt()'
collect2: error: ld returned 1 exit status

这是完全不合逻辑的,你只得到编译器的警告!

I just had this error in a program.
And I had this thinking : what happens if the method is marked as pure virtual in the constructor?

class Base {
public:
    virtual int getInt() = 0;
    
    Base(){
        printf("int=%d\n", getInt());
    }
};

class Derived : public Base {
    public:
        virtual int getInt() override {return 1;}
};

And... funny thing! You first get a warining by the compiler :

warning: pure virtual ‘virtual int Base::getInt() const’ called from constructor

And an error from ld!

/usr/bin/ld: /tmp/ccsaJnuH.o: in function `Base::Base()':
main.cpp:(.text._ZN4BaseC2Ev[_ZN4BaseC5Ev]+0x26): undefined reference to `Base::getInt()'
collect2: error: ld returned 1 exit status

This is totally illogic that you get just a warning from the compiler!

枫以 2024-07-30 07:07:05

从构造函数或析构函数调用虚函数是危险的,应尽可能避免。 所有 C++ 实现都应该调用当前构造函数中层次结构级别定义的函数版本,而不是进一步调用。

C++ FAQ Lite 在漂亮的第 23.7 节中介绍了这一点很好的细节。 我建议阅读该内容(以及常见问题解答的其余部分)以进行后续操作。

摘抄:

[...] 在构造函数中,虚拟调用机制被禁用,因为尚未发生派生类的重写。 对象是从基础向上构建的,“基础在派生之前”。

[...]

销毁是“派生类在基类之前”完成的,因此虚函数的行为与构造函数中一样:仅使用本地定义 - 并且不会调用重写函数以避免触及(现已销毁的)派生类部分对象。

编辑更正了大多数(感谢litb)

Calling virtual functions from a constructor or destructor is dangerous and should be avoided whenever possible. All C++ implementations should call the version of the function defined at the level of the hierarchy in the current constructor and no further.

The C++ FAQ Lite covers this in section 23.7 in pretty good detail. I suggest reading that (and the rest of the FAQ) for a followup.

Excerpt:

[...] 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”.

[...]

Destruction is done “derived class before base class”, so virtual functions behave as in constructors: Only the local definitions are used – and no calls are made to overriding functions to avoid touching the (now destroyed) derived class part of the object.

EDIT Corrected Most to All (thanks litb)

安穩 2024-07-30 07:07:05

在大多数面向对象语言中,从构造函数调用多态函数会导致灾难。 不同的语言在遇到这种情况时会有不同的表现。

基本问题是,在所有语言中,基类型必须在派生类型之前构造。 现在的问题是从构造函数调用多态方法意味着什么。 您期望它的表现如何? 有两种方法:在 Base 级别调用方法(C++ 风格)或在层次结构底部的未构造对象上调用多态方法(Java 风格)。

在 C++ 中,基类将在进入其自己的构造之前构建其虚拟方法表的版本。 此时,对虚拟方法的调用将最终调用该方法的基本版本,或者生成一个名为的纯虚拟方法,以防它在层次结构的该级别上没有实现。 完全构建 Base 后,编译器将开始构建 Derived 类,并将重写方法指针以指向层次结构的下一层中的实现。

class Base {
public:
   Base() { f(); }
   virtual void f() { std::cout << "Base" << std::endl; } 
};
class Derived : public Base
{
public:
   Derived() : Base() {}
   virtual void f() { std::cout << "Derived" << std::endl; }
};
int main() {
   Derived d;
}
// outputs: "Base" as the vtable still points to Base::f() when Base::Base() is run

在 Java 中,编译器将在构造的第一步、进入 Base 构造函数或 Derived 构造函数之前构建等效的虚拟表。 其含义是不同的(而且根据我的喜好更危险)。 如果基类构造函数调用在派生类中重写的方法,则该调用实际上将在派生级别处理,调用未构造对象上的方法,从而产生意外结果。 在构造函数块内初始化的派生类的所有属性尚未初始化,包括“final”属性。 具有在类级别定义的默认值的元素将具有该值。

public class Base {
   public Base() { polymorphic(); }
   public void polymorphic() { 
      System.out.println( "Base" );
   }
}
public class Derived extends Base
{
   final int x;
   public Derived( int value ) {
      x = value;
      polymorphic();
   }
   public void polymorphic() {
      System.out.println( "Derived: " + x ); 
   }
   public static void main( String args[] ) {
      Derived d = new Derived( 5 );
   }
}
// outputs: Derived 0
//          Derived 5
// ... so much for final attributes never changing :P

如您所见,调用多态(C++ 术语中的虚拟)方法是常见的错误来源。 在 C++ 中,至少你可以保证它永远不会在尚未构造的对象上调用方法......

Calling a polymorphic function from a constructor is a recipe for disaster in most OO languages. Different languages will perform differently when this situation is encountered.

The basic problem is that in all languages the Base type(s) must be constructed previous to the Derived type. Now, the problem is what does it mean to call a polymorphic method from the constructor. What do you expect it to behave like? There are two approaches: call the method at the Base level (C++ style) or call the polymorphic method on an unconstructed object at the bottom of the hierarchy (Java way).

In C++ the Base class will build its version of the virtual method table prior to entering its own construction. At this point a call to the virtual method will end up calling the Base version of the method or producing a pure virtual method called in case it has no implementation at that level of the hierarchy. After the Base has been fully constructed, the compiler will start building the Derived class, and it will override the method pointers to point to the implementations in the next level of the hierarchy.

class Base {
public:
   Base() { f(); }
   virtual void f() { std::cout << "Base" << std::endl; } 
};
class Derived : public Base
{
public:
   Derived() : Base() {}
   virtual void f() { std::cout << "Derived" << std::endl; }
};
int main() {
   Derived d;
}
// outputs: "Base" as the vtable still points to Base::f() when Base::Base() is run

In Java, the compiler will build the virtual table equivalent at the very first step of construction, prior to entering the Base constructor or Derived constructor. The implications are different (and to my likings more dangerous). If the base class constructor calls a method that is overriden in the derived class the call will actually be handled at the derived level calling a method on an unconstructed object, yielding unexpected results. All attributes of the derived class that are initialized inside the constructor block are yet uninitialized, including 'final' attributes. Elements that have a default value defined at the class level will have that value.

public class Base {
   public Base() { polymorphic(); }
   public void polymorphic() { 
      System.out.println( "Base" );
   }
}
public class Derived extends Base
{
   final int x;
   public Derived( int value ) {
      x = value;
      polymorphic();
   }
   public void polymorphic() {
      System.out.println( "Derived: " + x ); 
   }
   public static void main( String args[] ) {
      Derived d = new Derived( 5 );
   }
}
// outputs: Derived 0
//          Derived 5
// ... so much for final attributes never changing :P

As you see, calling a polymorphic (virtual in C++ terminology) methods is a common source of errors. In C++, at least you have the guarantee that it will never call a method on a yet unconstructed object...

左秋 2024-07-30 07:07:05

原因是 C++ 对象的构造就像洋葱一样,从内到外。 基类在派生类之前构造。 因此,在制造 B 之前,必须先制造 A。 当 A 的构造函数被调用时,它还不是 B,因此虚函数表仍然有 A 的 fn() 副本的条目。

The reason is that C++ objects are constructed like onions, from the inside out. Base classes are constructed before derived classes. So, before a B can be made, an A must be made. When A's constructor is called, it's not a B yet, so the virtual function table still has the entry for A's copy of fn().

合约呢 2024-07-30 07:07:05

C++ FAQ Lite 很好地涵盖了这一点:

本质上,在调用基类构造函数期间,对象还不是派生类型,因此调用的是基类型的虚函数实现,而不是派生类型的实现。

The C++ FAQ Lite Covers this pretty well:

Essentially, during the call to the base classes constructor, the object is not yet of the derived type and thus the base type's implementation of the virtual function is called and not the derived type's.

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