源码中出现很多关于私有继承的操作(默认不加public),但是通过using外部可以直接调用,这个是什么原理?

1介绍

(1)父类的public成员在private/protected继承后,在派生类中就成了private/protected权限而不是public权限,子类对象就不能再调用父类的public成员

(2)可能在父类中有100个public成员,在子类对象中只需要访问1个成员,其余99个成员都不需要访问,合适的做法是将100个父类的public成员以private/protected权限继承到子类,然后使用using关键字对需要访问的那一个父类public成员进行权限修改

2demo

2.1使用动态机制突破private权限

通过引用来突破private关键字的方式

 1#include <iostream>
 2using namespace std;
 3
 4class A
 5{
 6public:
 7	virtual void func() { cout << "func in A" << endl; }
 8};
 9
10class B : public A
11{
12private:
13	virtual void func() { cout << "func in B" << endl; }
14};
15
16int main()
17{
18	B b;
19
20	//b.func(); /* 错误:private函数对外不可见 */
21
22	/* 通过虚函数机制,可以突破private的限制,实现对B中的func进行调用 */
23	A &ra = b;
24	ra.func(); /* 输出:func in B */
25}

因为class B继承自class A(严格来说是public继承),所以依据动态绑定规则,class A的引用ra可以绑定到class B的实例b上。之后,通过ra调用func函数,实际调用到的就是class B中的func

2.2当做private来使用

如果把虚继承直接当做一个类内的private字段来操作,那就简单很多了

 1#include <iostream>
 2#include <string>
 3using namespace std;
 4
 5class A : private string
 6{
 7public:
 8    A() : string("yangyang48") {}
 9
10    size_t GetSize1() const
11    {
12        return ((const string *)this)->size();
13    }
14
15    size_t GetSize2() const
16    {
17        return string::size();
18    }
19};
20
21int main()
22{
23    A a;
24    cout << a.GetSize1() << endl; /* 输出: 10 */
25    cout << a.GetSize2() << endl; /* 输出: 10 */
26}

这里说明下,我们操作的string,实际上是basic_string;

 1//string
 2using string    = basic_string<char>;
 3//basic_string.h
 4template<typename _CharT, typename _Traits, typename _Alloc>
 5class basic_string
 6{
 7public:
 8    size_type
 9    size() const _GLIBCXX_NOEXCEPT
10    { return _M_string_length; }
11};

这里是string是一个共有方法的size(),只是用到了私有继承的方式。

实际上上面的两个get方法,本质还是公有函数中调用私有函数,变相增加了两个get方法,但不使用公有get直接调用也是不行的。

领导(string)给马屁精(class A)传达了一些信息(公开了.empty()、.size()、.substr()等public接口),并告诉马屁精:「这些资料,你要原封不动地公开给其它同事(外部调用)

马屁精(class A)转头回到同事中又开始鸡毛当令箭了(将继承方式改为 private继承)。抠抠搜搜地公开了一些信息,还美其名曰“帮大家整理总结”(将size()封装到GetSize1/GetSize2中供外部调用)。

同事(外部调用)一看所谓的“整理总结”,就是糊了个封皮封底(class A公布的接口中,除了无条件调用string的size()接口,别的什么也没做),这个时候同事们(外部调用)不干了,马屁精拍马屁(保持private继承)可以,但能不能别给其他人添乱(不要给接口套层壳,直接让外部调用string的原始接口),马屁精(class A)一看只能用using方式来替换自己的整理,毕竟如果不使用私有继承,自己就没啥用了也不需要using。

C++还确实提供了这种机制:在class A中使用using string::size;可以直接将string的size()接口提升为public权限。即外部可以直接调用该接口,形式上等同于针对该接口使用了public继承(string提供的其余接口仍旧是private继承)。

 1#include <iostream>
 2#include <string>
 3using namespace std;
 4
 5class A : private string
 6{
 7public:
 8    A() : string("yangyang48") {}
 9    using string::size;
10};
11
12int main() {
13    A a;
14    cout << a.size() << endl; /* 输出: 10 */
15}

输出结果跟上面一样

110

2.3源码分析

在源码中,也会存在使用using来提升继承成员的访问权限

 1//frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h
 2namespace android {
 3namespace scheduler {
 4    class Scheduler : impl::MessageQueue {
 5        using Impl = impl::MessageQueue;
 6
 7    public:
 8        Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags);
 9        virtual ~Scheduler();
10        ...
11        using Impl::initVsync;
12        using Impl::setInjector;
13        using Impl::getScheduledFrameTime;
14        using Impl::setDuration;
15        using Impl::scheduleFrame;
16    }; 
17}
18}
19
20namespace android {
21namespace impl {
22
23class MessageQueue : public android::MessageQueue {
24...
25public:
26    explicit MessageQueue(ICompositor&);
27
28    void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
29                   std::chrono::nanoseconds workDuration) override;
30    void setDuration(std::chrono::nanoseconds workDuration) override;
31    void setInjector(sp<EventThreadConnection>) override;
32    void waitMessage() override;
33    void postMessage(sp<MessageHandler>&&) override;
34    void scheduleFrame() override;
35    std::optional<Clock::time_point> getScheduledFrameTime() const override;
36};
37}    
38}

上述Scheduler使用默认继承impl::MessageQueue,默认继承为private继承,也就是当类的继承方式是私有继承时,基类中的公有和保护成员在派生类中变成私有成员,而基类的私有成员在派生类中不能直接访问

1//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
2void SurfaceFlinger::initScheduler(const sp<DisplayDevice>& display) {
3    mScheduler->initVsync(mScheduler->getVsyncDispatch(), *mFrameTimeline->getTokenManager(),
4                          configs.late.sfWorkDuration);
5}

但是在源码中,发现使用到了initVsync,这个可以直接使用调用到基类impl::MessageQueue中的方法。

往前面看,class Scheduler定义了using Impl::initVsync;这就说明权限不一致了,把using可以修改子类继承自父类的成员权限成public

(1)using可以修改子类继承自父类的成员权限成public,但并不是所有继承自父类的成员都可以修改成public权限;

(2) 如果成员在父类中本来就是private权限,那在子类中是无法使用using声明成public权限的;

总结:using只是用来找回在继承中损失的权限,给部分成员开特例;

3总结

总的来说,使用using可以让私有继承中原本父类的公有方法,在子类中恢复到公有的方法,可以在外部直接调用,这样可以隔离部分私有继承中需要不公开的方法的一种方式。

参考

[1] 正在起飞的蜗牛. 【C++入门】使用using重新定义继承的成员访问权限, 2022.

[2] Renekton_bhk. C++的private并没有听起来那么“保密”(virtual)(using), 2020.