私有继承基类函数如何被访问
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