指向派生 class 的 Base-class 指针无法访问派生-class 方法
Base-class pointer pointing to a derived class cannot access derived-class method
我正在学习 C++11 中的继承,我发现如果派生的 class 重新定义了一个虚函数名称但具有不同的原型,则 base-class 指针分配为指向派生 class 的指针只能访问函数的基础 class 版本。无法访问派生版本函数。我想知道为什么会这样。
class Enemy {
public:
virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
virtual void describe(int dummy) { std::cout << "Dragon"; }
};
在main
、
Dragon foo;
Enemy* pe = &foo;
pe->describe(); // Enemy
foo.describe(1); // Dragon
pe->describe(1); // no matching function, candidate is Enemy::describe()
根据我对虚函数表的了解,pe
指向的派生对象(即 foo
)应该有一个指向 Dragon
的 vtable 的 vpointer 成员。我还知道在派生 class 中重新定义一个函数名会隐藏基 class 中所有同名的函数。所以在Dragon的vtable中'describe'的地址应该是参数为int dummy
的函数。
但事实证明 pe
可以访问 Enemy
的方法版本,这应该是隐藏的。 pe
无法访问 Dragon
的方法版本,它应该在 pe
的 vtable 中。它的执行就好像使用了 Enemy
的 vtable 一样。为什么会这样?
更新:
我想现在我或多或少地了解了它背后的机制。这是我的假设:
由于是指向Enemy
的指针,程序会先在Enemy
的作用域中查找方法名。如果找不到该名称,编译器会报错。如果它不是虚拟的,则调用它。如果是virtual,则在Enemy
的vtable中记录方法的偏移量。然后程序使用这个偏移量访问目标对象的 vtable 中的正确方法。
如果该方法被正确覆盖,目标对象的 vtable 中该偏移量处的函数地址将被更改。否则,它将与 Enemy
的 vtable 中的函数地址相同,如示例中所示。
由于 Dragon
的 describe
与 int dummy
是不同的原型,因此它被添加到 Dragon
的 vtable 中原来的 describe
之后它继承自 Enemy。但是 int dummy
版本无法从 Enemy*
访问,因为 Enemy
的 vtable 甚至没有那个偏移量。
这是正确的吗?
同名不同签名的函数本质上是不同的函数。
通过在 Dragon
class 中声明 virtual void describe(int dummy)
,您已经声明了一个新的虚函数,而不是覆盖原来的虚函数(virtual void describe()
in Enemy
).您只能覆盖具有相同签名的虚函数。
您不能在指向 Enemy
的指针上调用 describe(1)
,因为 c++ 根据实例的编译时类型调用函数(尽管可以动态调度此类调用以调用实际的覆盖方法)。
在C++中,同名不同参数的函数是完全独立的函数,没有任何关系 .他们同名这一事实完全无关紧要。
这与您在基 class "apple" 和派生 class "banana" 中调用函数完全相同。由于基class中没有"banana"函数,显然不能在基class中调用它。 derived class 中的 banana 函数显然没有覆盖 base class.
中的函数
I also know that redefinition of a function name in the derived class
will hide all the functions of the same name in the base class.
这是不正确的。只有当它具有相同的名称和相同的参数(以及任何限定符,如果有或没有)时它才会隐藏它。
事实上你有:
class Enemy {
public:
virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
// void describe() override { Enemy::describe(); } // Hidden
virtual void describe(int dummy) { std::cout << "Dragon"; }
};
重载方法的选择是静态完成的:
pointers/references 在 Enemy
上只能看到 void Enemy::describe()
pointers/references 在 Dragon
上只能看到 void Dragon::describe(int)
(但可以明确访问 void Enemy::describe()
)。
然后使用运行时类型完成虚拟分派。
所以
Dragon foo;
Enemy* pe = &foo;
foo.describe(); // KO: Enemy::describe() not visible (1)
foo.Enemy::describe(); // OK: Enemy::describe()
foo.describe(1); // OK: Dragon::describe(int)
pe->describe(); // OK: Enemy::describe()
pe->describe(1); // KO: No Enemy::describe(int)
pe->Dragon::describe(1);// KO: Dragon is not a base class of Enemy
(1) 可以通过将 Dragon
更改为
来修复
class Dragon : public Enemy {
public:
using Enemy::describe; // Unhide Enemy::describe()
virtual void describe(int dummy) { std::cout << "Dragon"; }
};
我正在学习 C++11 中的继承,我发现如果派生的 class 重新定义了一个虚函数名称但具有不同的原型,则 base-class 指针分配为指向派生 class 的指针只能访问函数的基础 class 版本。无法访问派生版本函数。我想知道为什么会这样。
class Enemy {
public:
virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
virtual void describe(int dummy) { std::cout << "Dragon"; }
};
在main
、
Dragon foo;
Enemy* pe = &foo;
pe->describe(); // Enemy
foo.describe(1); // Dragon
pe->describe(1); // no matching function, candidate is Enemy::describe()
根据我对虚函数表的了解,pe
指向的派生对象(即 foo
)应该有一个指向 Dragon
的 vtable 的 vpointer 成员。我还知道在派生 class 中重新定义一个函数名会隐藏基 class 中所有同名的函数。所以在Dragon的vtable中'describe'的地址应该是参数为int dummy
的函数。
但事实证明 pe
可以访问 Enemy
的方法版本,这应该是隐藏的。 pe
无法访问 Dragon
的方法版本,它应该在 pe
的 vtable 中。它的执行就好像使用了 Enemy
的 vtable 一样。为什么会这样?
更新: 我想现在我或多或少地了解了它背后的机制。这是我的假设:
由于是指向Enemy
的指针,程序会先在Enemy
的作用域中查找方法名。如果找不到该名称,编译器会报错。如果它不是虚拟的,则调用它。如果是virtual,则在Enemy
的vtable中记录方法的偏移量。然后程序使用这个偏移量访问目标对象的 vtable 中的正确方法。
如果该方法被正确覆盖,目标对象的 vtable 中该偏移量处的函数地址将被更改。否则,它将与 Enemy
的 vtable 中的函数地址相同,如示例中所示。
由于 Dragon
的 describe
与 int dummy
是不同的原型,因此它被添加到 Dragon
的 vtable 中原来的 describe
之后它继承自 Enemy。但是 int dummy
版本无法从 Enemy*
访问,因为 Enemy
的 vtable 甚至没有那个偏移量。
这是正确的吗?
同名不同签名的函数本质上是不同的函数。
通过在 Dragon
class 中声明 virtual void describe(int dummy)
,您已经声明了一个新的虚函数,而不是覆盖原来的虚函数(virtual void describe()
in Enemy
).您只能覆盖具有相同签名的虚函数。
您不能在指向 Enemy
的指针上调用 describe(1)
,因为 c++ 根据实例的编译时类型调用函数(尽管可以动态调度此类调用以调用实际的覆盖方法)。
在C++中,同名不同参数的函数是完全独立的函数,没有任何关系 .他们同名这一事实完全无关紧要。
这与您在基 class "apple" 和派生 class "banana" 中调用函数完全相同。由于基class中没有"banana"函数,显然不能在基class中调用它。 derived class 中的 banana 函数显然没有覆盖 base class.
中的函数I also know that redefinition of a function name in the derived class will hide all the functions of the same name in the base class.
这是不正确的。只有当它具有相同的名称和相同的参数(以及任何限定符,如果有或没有)时它才会隐藏它。
事实上你有:
class Enemy {
public:
virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
// void describe() override { Enemy::describe(); } // Hidden
virtual void describe(int dummy) { std::cout << "Dragon"; }
};
重载方法的选择是静态完成的:
pointers/references 在
Enemy
上只能看到void Enemy::describe()
pointers/references 在
Dragon
上只能看到void Dragon::describe(int)
(但可以明确访问void Enemy::describe()
)。
然后使用运行时类型完成虚拟分派。
所以
Dragon foo;
Enemy* pe = &foo;
foo.describe(); // KO: Enemy::describe() not visible (1)
foo.Enemy::describe(); // OK: Enemy::describe()
foo.describe(1); // OK: Dragon::describe(int)
pe->describe(); // OK: Enemy::describe()
pe->describe(1); // KO: No Enemy::describe(int)
pe->Dragon::describe(1);// KO: Dragon is not a base class of Enemy
(1) 可以通过将 Dragon
更改为
class Dragon : public Enemy {
public:
using Enemy::describe; // Unhide Enemy::describe()
virtual void describe(int dummy) { std::cout << "Dragon"; }
};