我有一个指向派生类对象的基类指针。我在下面的代码中使用两种不同的方式调用foo()
函数。为什么Derived::foo()
在第一种情况下被调用?不应(*obj).foo()
调用Base::foo()
函数,因为它已被解除引用?
class Base
{
public:
Base() {}
virtual void foo() { std::cout << "Base::foo() called" << std::endl; }
virtual ~Base() {};
};
class Derived: public Base
{
public:
Derived() : Base() {}
virtual void foo() { std::cout << "Derived::foo() called" << std::endl; }
virtual ~Derived() {};
};
int main() {
Base* obj = new Derived();
// SCENARIO 1
(*obj).foo();
// SCENARIO 2
Base obj1 = *obj;
obj1.foo();
return 0;
}
答案 0 :(得分:16)
// SCENARIO 1 (*obj).foo();
请注意
obj
在这里用词不当,因为它不是指对象,而是指指针,(*ptr).foo()
只是一种迂回的方式ptr->foo()
。 *ptr
不会生成对象,但会在 引用 Base&
中生成对象。通过引用进行的虚函数调用受动态调度的影响,就像通过指针调用一样。
// SCENARIO 2 Base obj1 = *ptr; obj1.foo();
你在这里做的是通过slicing创建一个全新的对象:它只有*ptr
的基类部分。你想要的是这个:
Base& ref = *ptr;
ref.foo();
答案 1 :(得分:3)
场景2创建一个Base类型的全新对象。因此,当我们执行obj1.foo()
时,对象根本不是派生的;我们无法调用派生函数。
然而,在场景1中,对象实际上是Derived的一个实例,我们通过Base指针访问它。这正是虚拟功能所针对的情况;使用派生类的实现。
答案 2 :(得分:1)
如果您对实现有所了解,这会有所帮助。在第二个场景中,您实际上是在创建一个Base类型的新对象,它将带有一个新的虚函数表。但是在第一个场景中*obj
将“指向”,或者更确切地说是引用一个仍然具有Derived类型对象的虚函数表的对象。
答案 3 :(得分:1)
作为其他答案的补充。
对于场景2中发生的事情的技术术语是对象切片。
这是维基百科条目:
http://en.wikipedia.org/wiki/Object_slicing
这是关于对象切片的stackoverflow的另一个问题:
答案 4 :(得分:1)
在第一种情况下,由于上面解释的明显原因,将调用foo()
的派生版本。除了其他答案,*(*Obj).func()*
与*Obj->func()*
同义。
在第二种情况下,通过复制构造函数实例化类Base
的新对象,因为它是Base
类对象,它将调用Base
类foo()
类}。
答案 5 :(得分:0)
多态性对引用(取消引用指针的结果)起作用,就像在指针上一样。
答案 6 :(得分:0)
你是什么意思“因为它已被解除引用”?
基类指针obj指向派生类对象,因为你声明了函数foo()virtual,所以将调用派生类foo()。
答案 7 :(得分:0)
(这是一个相当奇怪的问题。我宁愿有人问为什么在第二种情况下Derived::foo
没有被调用。)
在C ++语言中,调用哪个版本的虚函数完全独立于已经“取消引用”的内容和内容。解除引用没有任何区别。唯一重要的是调用中使用的对象的动态类型。
在第一种情况下调用Derived::foo
,因为*obj
对象的动态类型为Derived
。
在第二种情况下,动态类型obj1
为Base
,因此调用Base::foo
。
换句话说,一切都按预期工作。这让一个奇迹让你问你的问题。是什么让你期待不同的东西?