深入理解C++友元机制:实现代码与理论的完美融合

发表时间: 2024-05-31 16:04

主流程序语言标配之一就是支持OOP编程范式。C++也不例外。让我们看看,C++在实现OOP编程范式上的特色吧。

提出问题

我们先来看一段代码。定义一个point类,表示笛卡尔坐标系里一个点。遵循OOP原则,把成员设置为私有。

class Point{public:    Point(double x = 0.0, double y = 0.0) : x{x}, y{y} {}private:    double x;    double y;};

如此以来,我们要访问从类外访问这个类的成员,只能通过共有接口。这确实是OOP的初心。让我们来定义这个接口。getX方法和geY方法。

class Point{public:    Point(double x = 0.0, double y = 0.0) : x{x}, y{y} {}    double getX() { return x; }    double getY() { return y; }private:    double x;    double y;};

现在,让我们来定义一个函数,计算两点的距离。

#include <cmath>double distance(Point const& pt1, Point const& pt2){  double dx = pt1.getX() - pt2.getX();  double dy = pt1.getY() - pt.getY();  return sqrt(dx*dx + dy*dy);}

可能我们在编写代码途中就会发现,即便是获取一个点的坐标,也需要调用getX和getY,代码变长,没那么自然。但为了伟大的”封装“原则,只能如此了。

这个问题对于所有支持面向对象的设计者都是一个有趣的挑战,C++给出的答案很有创造性。那就是创立了”友元“的概念。

解决问题

上面讲到了,我们在想,有没有一个好办法,可以让外部函数直接调用一个类的私有成员,又不破坏OOP的原则呢。有啊。那就是增加一个“友元“的概念。

所谓”友元“,就是针对一个特定函数,它是目标类的好的朋友。好朋友就不当外人了,私有的东西也分享啦。

说得容易,做起来也容易。在类里面打一个招呼,说某一个函数是“好朋友”哈。看代码。

class Point{public:    Point(double x = 0.0, double y = 0.0) : x{x}, y{y} {}    double getX() { return x; }    double getY() { return y; }    friend double distance(Point const& pt1, Point const& pt2); // 好朋友在此private:    double x;    double y;};

然后我们在distance 函数里就愉快的直接调用类的成员。

#include <cmath>double distance(Point const& pt1, Point const& pt2){  double dx = pt1.x - pt2.x;  double dy = pt1.y - pt2.y;  return sqrt(dx*dx + dy*dy);}

在distance函数看来,Point类是开放的,可以直接使用私有部分的成员等。这给写代码的程序员带来的不仅是代码更短,而且在心智上少了去调用接口的负担,想到哪就写到哪,很自然。

这就是所谓的友元的概念和如何实现。

有必要说一下,慎用友元,尊重面向对象原则,别滥用友元。不分场景,动辄用上友元,就会让你的代码没有用上面向对象带来的好处,惹上一堆毛病,那还不如不用面向对象范式来写程序呢。

对比

C#和Java采用了不同的办法来应对上面谈论的挑战。在语法上引入了getter等语法糖,让程序写出看起来很美的代码,代价是必须学习和记忆更多的语法,增加心智负担。相比之下,C++的友元显得更有效率,更实用。

总结

友元让C++在支持面向对象的同时,坚守了它的设计初心,那就是竭尽全力在抽象和实现各矛盾体,面向机器,寻找最高效的方法,面向程序员,寻找最自然的表达方法。