C++的private并没有听起来那么“保密”(virtual)(using)

前言

从常识角度看,private权限的下的成员对外是永远不可见的,而且private成员也不会公开给继承的子类,所以似乎private成员(尤其是成员函数)似乎具有了很强的安全性——既然无论如何别人都使用不了,那么该class的设计者可以完全控制private成员的使用时机,所以可以简化一些错误判断或者时序控制工作。

然而实际则不尽然,在一些特殊情况下,仍旧有可能存在突破private限制,进行函数调用/变量修改的情况。

利用虚函数机制突破private权限

注意:以下代码是编译不过的,main函数中的b.func();处有报错提示中。

#include <iostream>
using namespace std;

class A
{
public:
	virtual void func() { cout << "func in A" << endl; }
};

class B : public A
{
private:
	virtual void func() { cout << "func in B" << endl; }
};

int main()
{
	B b;

	b.func(); /* 错误:private函数对外不可见 */

	/* 通过虚函数机制,可以突破private的限制,实现对B中的func进行调用 */
	A &ra = b;
	ra.func(); /* 输出:func in B */
}

以上代码中,class B从class A继承了虚成员函数func,并将权限改为private,根据报错提示可知直接通过class B的实例b进行调用是不可行的。

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

小结

如果重定义了继承到的虚函数,需要注意查看父类中,该虚函数是否为public权限,如果是,则不要尝试将其权限改为private,因为这可能会产生不必要的信任,因为通过动态绑定机制仍可以绕过private限制,来调用重定义的虚函数。而这个调用的时机显然是可以任由别人来插手的,所以你还是需要像设计一个public的函数一样进行必要的容错、时序等方面的考虑。

回过头来看,完全不应该修改继承来的虚函数的权限,父类是什么权限,子类也应该保持这个权限,这也是最不容易出错的选择了。

一种不算突破的突破

如下代码中:
class A继承自C++标准库的string(注意是private继承)
简便起见,A的默认构造函数会调用父类(string)的构造函数将其初始化为10个'a'的字符串
由于某种神秘的需求,class A需要实现一个公开的接口,该接口返回父类string对象的长度

#include <iostream>
#include <string>
using namespace std;

class A : private string
{
public:
	A() : string(10, 'a') {}

	size_t GetSize1() const
	{
		return ((const string *)this)->size();
	}

	size_t GetSize2() const
	{
		return string::size();
	}
};

int main()
{
	A a;
	cout << a.GetSize1() << endl; /* 输出: 10 */
	cout << a.GetSize2() << endl; /* 输出: 10 */
}

为满足所谓「神秘的需求」,以上代码中提供了2种接口实现方式:

其中GetSize1利用动态绑定机制,将const A *类型的this转换成const string *类型,然后调用了string类的size()函数

GetSize2则直接指明了调用父类的size()函数,简单、直观、粗暴、高效,相对GetSize1显然是更优的方案

有没有更便捷的方式?

上述2种公开size()接口的实现方式多多少少都有点啰嗦:string的各种接口尽管在继承给A时被降级成了private权限,但是这些被隐藏起来的接口对class A的内部是完全公开的,而class A想将其中的size()接口原封不动地公开出来时,却不得不将其封装到一个新的接口中来实现间接调用,string类既然公开了size()接口,说明其是允许外部随时调用的,但是class A这么继承下来,却搞得要层层审批才能用,是不是有点不太合理?

下一节我们把这个问题用拟人化的方式再描述一下,如果你能理解上面这段话的需求,则可以跳过下一节。

先讲个故事

领导(string)给马屁精(class A)传达了一些信息(公开了.empty()、.size()、.substr()等public接口),并告诉马屁精:「这些资料,你要原封不动地公开给其它同事,有不理解的地方可以互相探讨一下,实在不明白的,下周周会上提出来,我再做解释。但决不允许自由发挥,不懂装懂。」(领导想要的是 public继承

马屁精做出一个心领神会却又不明所以的神秘微笑,转头回到同事中又开始鸡毛当令箭了:「哎呀……都是些高级管理层的内部机密,老板这么忙,我也不好意思总请示他,你们先回去,我再看看吧」(将继承方式改为 private继承)。

过了若干天,马屁精抠抠搜搜地公开了一些信息,还美其名曰“帮大家整理总结”(将size()封装到GetSize1/GetSize2中供外部调用)。

大家把资料拿到手一看,你这所谓的“整理总结”,就是糊了个封皮封底(class A公布的接口中,除了无条件调用string的size()接口,别的什么也没做),折腾这么一大圈,你在干什么?你想拍马屁(保持private继承)可以,但能不能别给其他人添乱(不要给接口套层壳,直接让外部调用string的原始接口

故事讲完了

核心矛盾是没人需要马屁精的“整理总结”,大家只想看真正的原始资料,这样是失真最小的信息传递方式。将这个需求转换到前面的编程问题中,就成了:你想private继承,我没意见,但能不能外部通过class A的实例对象直接调用string的size()接口?

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

#include <iostream>
#include <string>
using namespace std;

class A : private string
{
public:
	A() : string(10, 'a') {}
	using string::size;
};

int main()
{
	A a;
	cout << a.size() << endl; /* 输出: 10 */
}

注意,用于公开接口的using string::size;语句既没有指明函数的返回值,也没有指明函数的参数,如果父类有该函数的多个重载,则这一条语句会将所有的重载形式全部公开出来。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值