提问人:Foma Roje 提问时间:11/17/2023 最后编辑:Foma Roje 更新时间:11/17/2023 访问量:113
这在 C++ 中是如何工作的?通过基类公开派生类的私有虚拟成员
How does this work in C++? exposing private virtual member of a derived class through base class
问:
我找到了一个类似的代码片段。让我们假设有充分的理由在派生类 B 中隐藏虚函数。但是,可以通过基类公开此私有成员,如下所示:
class A {
public:
virtual void print() const { std::cout << "hello A" << std::endl;}
};
class B: public A {
private:
void print() const override { std::cout << "hello B" << std::endl;}
};
int main() {
const A &a = B();
a.print(); // works unexpectedly (prints "hello B")
const B &b = B();
b.print(); // does not work (as expected)
}
知道为什么有效,即使 A 应该在运行时动态绑定到 B 类?还是 C++ 在运行时忽略私有/公共分类器?a.print()
我执行了上面的代码,没想到可以工作,因为在派生类中是私有的。a.print()
print()
答:
这里没有忽略任何内容。
const A &a = B(); a.print();
简而言之:您正在调用对 的引用。 在 中。直到现在,动态调度才轮到它了。方法是,对象的动态类型是 ,所以我们调用 。print
A
print
public
A
virtual
B
B::print
请考虑一下,如果这将按预期工作,则访问将取决于对象的动态类型,并且像这样的代码
void foo(const A& a) {
a.print();
}
无法决定是否可以调用。但事实并非如此。要决定是否可以访问,我们只需要查看 .a.print()
a.print()
A
访问控制检查是按名称进行的,在使用它的表达式的静态类型的上下文中。 是公开的,所以是有效的。A::print
a.print()
虚拟调度与访问控制无关,因此绑定到它调用的 。如果虚拟调度考虑访问控制,则必须将其推迟到运行时进行检查,至少在某些情况下是这样。例如a
B
B::print
extern bool coinflip();
const A &a = coinflip() ? A{} : B{};
a.print();
这有什么出乎意料的?
const A &a = B();
a.print();
让我们一步一步地看看发生了什么。
当您调用赋值运算符时,您可以假设正确的运算符上的事情首先发生,因此您创建了未命名的临时对象(重要提示:该对象从现在到作用域末尾都存在于内存中,因为您正在创建对该对象的引用)。使用赋值运算符的左侧,您说 const 引用现在引用该对象。您的引用类型为 ,并且 C++ 允许该转换(强制转换):派生类到基类。但是,这种转换不会改变存储未命名对象的内存。因此,当您使用引用访问成员函数时,您实际上是在调用 .=
class B
a
a
class A
class B
print()
a
B.print()
你唯一能完成的事情是,由于方法在内部是公共的,你可以在类之外调用它,正如有人在评论中已经说过的那样。class A
print()
class A
评论
=
=
=
是等号,这里不是赋值运算符。
评论
a.print();
print
A
A
print