这在 C++ 中是如何工作的?通过基类公开派生类的私有虚拟成员

How does this work in C++? exposing private virtual member of a derived class through base class

提问人:Foma Roje 提问时间:11/17/2023 最后编辑:Foma Roje 更新时间:11/17/2023 访问量:113

问:

我找到了一个类似的代码片段。让我们假设有充分的理由在派生类 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()

C++语言

评论

0赞 463035818_is_not_an_ai 11/17/2023
为什么行不通? 是公共的,并且您通过引用a.print(); printAA
0赞 Some programmer dude 11/17/2023
当你说“没有按预期工作”时,你期望什么?实际发生了什么?请编辑您的问题以提供更多详细信息。
0赞 NathanOliver 11/17/2023
这是预期行为。 在基类中是公共的,因此通过基类访问它没有问题。访问控制是不可传递的。print
0赞 Foma Roje 11/17/2023
“不起作用(如预期)”=实际上证实了我的期望,即它不应该起作用。无论如何,谢谢,我认为@Caleth的解释是有道理的。
2赞 Some programmer dude 11/17/2023
“或者 C++ 在运行时会忽略私有/公共分类器吗?”这些是用于访问的编译时说明符。一旦代码被构建,类本身就不再真正存在了,只有它们的实现(函数)和对象的内存区域。

答:

6赞 463035818_is_not_an_ai 11/17/2023 #1

这里没有忽略任何内容。

const A &a = B();
a.print(); 

简而言之:您正在调用对 的引用。 在 中。直到现在,动态调度才轮到它了。方法是,对象的动态类型是 ,所以我们调用 。printAprintpublicAvirtualBB::print


请考虑一下,如果这将按预期工作,则访问将取决于对象的动态类型,并且像这样的代码

 void foo(const A& a) {
        a.print();
 }

无法决定是否可以调用。但事实并非如此。要决定是否可以访问,我们只需要查看 .a.print()a.print()A

2赞 Caleth 11/17/2023 #2

访问控制检查是按名称进行的,在使用它的表达式的静态类型的上下文中。 是公开的,所以是有效的。A::printa.print()

虚拟调度与访问控制无关,因此绑定到它调用的 。如果虚拟调度考虑访问控制,则必须将其推迟到运行时进行检查,至少在某些情况下是这样。例如aBB::print

extern bool coinflip();
const A &a = coinflip() ? A{} : B{};
a.print();
1赞 Veljko 11/17/2023 #3

这有什么出乎意料的?

const A &a = B();
a.print();

让我们一步一步地看看发生了什么。 当您调用赋值运算符时,您可以假设正确的运算符上的事情首先发生,因此您创建了未命名的临时对象(重要提示:该对象从现在到作用域末尾都存在于内存中,因为您正在创建对该对象的引用)。使用赋值运算符的左侧,您说 const 引用现在引用该对象。您的引用类型为 ,并且 C++ 允许该转换(强制转换):派生类到基类。但是,这种转换不会改变存储未命名对象的内存。因此,当您使用引用访问成员函数时,您实际上是在调用 .=class Baaclass Aclass Bprint()aB.print()

你唯一能完成的事情是,由于方法在内部是公共的,你可以在类之外调用它,正如有人在评论中已经说过的那样。class Aprint()class A

评论

2赞 463035818_is_not_an_ai 11/17/2023
此代码中没有赋值运算符
0赞 Veljko 11/17/2023
@463035818_is_not_an_ai 那是什么?=
2赞 463035818_is_not_an_ai 11/17/2023
en.cppreference.com/w/cpp/language/initialization
1赞 Veljko 11/17/2023
@463035818_is_not_an_ai 另一方面。我指的是赋值运算符,而不是赋值过程(语句)。这是初始化过程(语句),但您正在使用赋值运算符来完成它。没有术语初始化运算符,当然是一个运算符,那么你怎么称呼它呢?=
1赞 463035818_is_not_an_ai 11/18/2023
=是等号,这里不是赋值运算符。