私有继承基类函数如何被访问

  C++面向对象中的继承模式有三种:公有继承(关键字public)、保护继承(关键字protected)和私有继承(关键字private)。在开发中以公有继承占绝大多数情况,保护和私继承使用情况很少。写这篇博客介绍私有继承,源于一个问题。下面给大家详细道来。

一、限制C++接口问题

  有一个Rectangle类,它向外界提供了三个接口,类定义如下:

 1 struct Position
 2 {
 3     int x;
 4     int y;
 5 };
 6 
 7 class Rectangle
 8 {
 9 public:
10     explicit Rectangle(const Position& pos, int w, int h)
11         : _pos(pos)
12         , _w(w)
13         , _h(h)
14     {}
15     int GetWidth() const { return _w; }
16     int GetHeight() const { return _h; }
17     void Draw()
18     {
19         std::cout << "Rectangle::Draw()\n";
20     }
21 private:
22     Position _pos;
23     int _w;
24     int _h;
25 };

  现在,我希望编写一个新的类,只让他有Draw的能力。该怎么办?实现的方式多种多样,但是我们用继承的方式实现这个需求。

二、继承如何限制C++接口

  我们知道C++面向对象中的基础有三种模式,其中公有继承不改变基类的访问属性,所以无法起到限制上面GetWidth和GetHeight接口。保护继承和私有继承都可以限制基类的公共接口被外部直接访问;保护和私有的区别在于,保护的方法可以被子类访问,私有的方法不行。保护和私有继承都可以限制C++接口的能力。代码如下:

 1 class DrawableRectangle : protected Rectangle
 2 {
 3 public:
 4     explicit DrawableRectangle(const Rectangle& rect)
 5         : Rectangle(rect)
 6     {}
 7 };
 8 
 9 int main()
10 {
11     Rectangle rect(Position{ 0, 0 }, 10, 10);
12     DrawableRectangle drawableRect(rect);
13     drawableRect.Draw();
14 }

  或者

 1 class DrawableRectangle : private Rectangle
 2 {
 3 public:
 4     explicit DrawableRectangle(const Rectangle& rect)
 5         : Rectangle(rect)
 6     {}
 7 };
 8 
 9 int main()
10 {
11     Rectangle rect(Position{ 0, 0 }, 10, 10);
12     DrawableRectangle drawableRect(rect);
13     drawableRect.Draw();
14 }

  当同学们兴致勃勃地编写上面的代码,点击编译的时候,突然发现:

       

  或 

  

  发现编译根本无法正常通过。这样写确实无法正常通过的,这边“坑爹”吗?有的同学马上想到了友元类可以解决这个问题,于是敲动键盘写下如下代码:

 1 class DrawableRectangle : private Rectangle
 2 {
 3 public:
 4     explicit DrawableRectangle(const Rectangle& rect)
 5         : Rectangle(rect)
 6     {}
 7 
 8     friend class Client;
 9 };
10 
11 class Client
12 {
13 public:
14     void Test()
15     {
16         Rectangle rect(Position{ 0, 0 }, 10, 10);
17         DrawableRectangle drawableRect(rect);
18         drawableRect.Draw();
19     }
20 };
21 
22 int main()
23 {
24     Client t;
25     t.Test();
26 }

  恭喜你,这样确实能起到限制C++接口的同时,能够访问基类的接口。编译并运行的结构如下: 

      

   但是你却忽略了友元的弊端,破坏封装性,友元确实能够访问类的保护和私有方法或属性,这里是所有哦!例如我还是可以正常访问GetWidth和GetHeight方法:

 1 class Client
 2 {
 3 public:
 4     void Test()
 5     {
 6         Rectangle rect(Position{ 0, 0 }, 10, 10);
 7         DrawableRectangle drawableRect(rect);
 8         drawableRect.Draw();
 9         std::cout << "width<" << drawableRect.GetHeight() 
10             << "> height<" << drawableRect.GetHeight() << "\n";
11 
12     }
13 };
14 
15 int main()
16 {
17     Client t;
18     t.Test();
19 }

  

   绕这么大的弯子,还是没有解决我之前提到的问题。是不是这个问题根本就不能用继承解决?

三、using关键字

  刚开始我也这么认为,但是直到看到如下代码:

 1 class DrawableRectangle : private Rectangle
 2 {
 3 public:
 4     explicit DrawableRectangle(const Rectangle& rect)
 5         : Rectangle(rect)
 6     {}
 7 
 8     using Rectangle::Draw;
 9 };
10 int main()
11 {
12     Rectangle rect(Position{ 0, 0 }, 10, 10);
13     DrawableRectangle drawableRect(rect);
14     drawableRect.Draw();
15     drawableRect.GetWidth();
16     drawableRect.GetHeight();
17 }

  

  编译只报GetWidth和GetHeight接口不可访问的错误,并没有报Draw接口。我们屏蔽掉报错的接口,再次编译后运行:

       

  保护或私有继承的子类中,使用这个using关键字指定基类某个方法或属性都可以在外界通过子类自己调用吗?

  基类方法或属性是public:

 1 class Rectangle
 2 {
 3 public:
 4     explicit Rectangle(const Position& pos, int w, int h)
 5         : _pos(pos)
 6         , _w(w)
 7         , _h(h)
 8     {}
 9     int GetWidth() const { return _w; }
10     int GetHeight() const { return _h; }
11     void Draw()
12     {
13         std::cout << "Rectangle::Draw()\n";
14     }
15     int _a;
16 private:
17     Position _pos;
18     int _w;
19     int _h;
20 };
21 
22 class DrawableRectangle : private Rectangle
23 {
24 public:
25     explicit DrawableRectangle(const Rectangle& rect)
26         : Rectangle(rect)
27     {}
28 
29     using Rectangle::Draw;
30     using Rectangle::_a;
31 };
32 int main()
33 {
34     Rectangle rect(Position{ 0, 0 }, 10, 10);
35     DrawableRectangle drawableRect(rect);
36     drawableRect.Draw();
37     drawableRect._a = 10;
38 }

  

   基类方法或属性是protected:

 1 class Rectangle
 2 {
 3 public:
 4     explicit Rectangle(const Position& pos, int w, int h)
 5         : _pos(pos)
 6         , _w(w)
 7         , _h(h)
 8     {}
 9     int GetWidth() const { return _w; }
10     int GetHeight() const { return _h; }
11     void Draw()
12     {
13         std::cout << "Rectangle::Draw()\n";
14     }
15     int _a;
16 protected:
17     void Fn()
18     {
19         std::cout << "Rectangle::Fn() protected\n";
20     }
21     int _b;
22 private:
23     Position _pos;
24     int _w;
25     int _h;
26 };
27 
28 class DrawableRectangle : private Rectangle
29 {
30 public:
31     explicit DrawableRectangle(const Rectangle& rect)
32         : Rectangle(rect)
33     {}
34 
35     using Rectangle::Draw;
36     using Rectangle::_a;
37     using Rectangle::Fn;
38     using Rectangle::_b;
39 };
40 int main()
41 {
42     Rectangle rect(Position{ 0, 0 }, 10, 10);
43     DrawableRectangle drawableRect(rect);
44     drawableRect.Fn();
45     drawableRect._b = 10;
46 
47 }

  

  基类方法或属性是private:

 1 class Rectangle
 2 {
 3 public:
 4     explicit Rectangle(const Position& pos, int w, int h)
 5         : _pos(pos)
 6         , _w(w)
 7         , _h(h)
 8     {}
 9     int GetWidth() const { return _w; }
10     int GetHeight() const { return _h; }
11     void Draw()
12     {
13         std::cout << "Rectangle::Draw()\n";
14     }
15     int _a;
16 protected:
17     void Fn()
18     {
19         std::cout << "Rectangle::Fn() protected\n";
20     }
21     int _b;
22 private:
23     void Fn1()
24     {
25     }
26 private:
27     Position _pos;
28     int _w;
29     int _h;
30 };
31 
32 class DrawableRectangle : private Rectangle
33 {
34 public:
35     explicit DrawableRectangle(const Rectangle& rect)
36         : Rectangle(rect)
37     {}
38 
39     using Rectangle::Draw;
40     using Rectangle::_a;
41     using Rectangle::Fn;
42     using Rectangle::_b;
43 
44     using Rectangle::Fn1;
45     using Rectangle::_w;
46 };

  

   语法报错,所以说除了private修饰的成员方法和属性外,另外两种都可以通过using关键正常指定外界可以访问的方法或接口。保护继承同私有继承作用类似,这里不再列举处理进行验证。(上面都是通过visual studio2019编译的,对于低版本的,尤其不支持using关键字的编译器就不支持这种用法了)。

  公用继承下,子类和基类是:is-a的关系,即子类是基类的一种类型。(这里不考虑继承抽象类,子类和抽象类是:has-a关系)私有继承不在让子类和基类拥有:is-a的关系,而是变成:has-a的关系。这种继承模式下,能够起到适当屏蔽基类中的公共方法,到达子类不同于基类的行为的作用。

 

参考

1、Restricting an interface in C++ - Fluent C++ (fluentcpp.com)

2、The quest of private inheritance in C++ | Sandor Dargo's Blog

posted @ 2021-05-24 23:02  blackstar666  阅读(576)  评论(0编辑  收藏  举报