C++友元:跨墙访问的三种姿势
目录
友元
友元之普通函数形式
友元之成员函数形式
友元类
友元的特点
友元
-
什么叫友元?
一般来说,类的私有成员只能在类的内部访问,类之外是不能访问它们的。但如果将其他类/函数设置为类的友元,那么友元类/函数就可以在前一个类的类定义之外访问其私有成员了。用friend关键字声明友元。
将类比作一个家庭,类的private 成员相当于家庭的秘密,一般的外人当然不允许探听这些秘密的,只有 friend 才有资格探听这些秘密。
友元的三种形式:普通函数、成员函数、友元类
友元之普通函数形式
示例:程序中有Point类,需要求取两个点的距离。按照设想,我们定义一个普通函数distance,接收两个Point对象作为参数,通过公式计算这两个点之间的距离。但Point的_ix和 _iy是私有成员,在类外不能通过对象访问,那么可以将distance函数声明为Point类的友元函数,之后就可以在distance函数中访问Point的私有成员了。
class Point{
public:Point(int x, int y): _ix(x), _iy(y){}
friendfloat distance(const Point & lhs, const Point & rhs);
private:int _ix;int _iy;
};
float distance(const Point & lhs, const Point & rhs){return sqrt((lhs._ix - rhs._ix)*(lhs._ix - rhs._ix) +(lhs._iy - rhs._iy)*(lhs._iy - rhs._iy));
}
友元之成员函数形式
假设类A有一个成员函数,该成员函数想去访问另一个类B类中的私有成员变量。这时候则可以在第二个类B中,声明第一个类A的那个成员函数为类B的友元函数,这样第一个类A的某个成员函数就可以访问第二个类B的私有成员变量了。
我们试验一下,以另一种方式实现上面的需求,如果distance函数不再是一个普通函数,而是Line类的一个成员函数,也就是说需要在一个类(Line)的成员函数中访问另一个类(Point)的私有成员,那么又该如何实现呢?
-
如果将Point类定义在Line类之前,Line类的成员函数要访问Point类的私有成员,需要在Point类中将Line的这个成员函数设为友元函数——此时编译器并不认识Line类;
-
如果将Line类定义在Point类之前,那么distance函数需要接受两个const Point &作为参数——此时编译器不认识Point类;
解决方法:
——在Line前面做一个Point类的前向声明;
——但如果将distance的函数体写在Line类中,编译器虽然知道了有一个Point类,但并不知道Point类具体有什么成员,所以此时在函数体中访问_ix、 _iy都会报错,编译器并不认识它们;
思考一下,有什么办法可以解决这个问题呢?
class Point;//前行声明
//只有前向声明,只知道有piont这个类,不知道point是怎么实现的,但不能访问point的内容,那么我们在line类中
//只做函数的声明,不做定义
class Line
{//需要前向声明,使编译器知道有piont这个类public:float destance(const Point &lhs,const Point &rhs);
};
class Point
{public:Point(int x,int y):_ix(x),_iy(y){}//friend friend float Line::destance(const Point &lhs,const Point &rhs);
private:int _ix;int _iy;
};
//point 有什么东西编译器已经知道,现在对destance做定义就可以了,在外面是普通函数,就上作用域
float Line::destance(const Point &lhs,const Point &rhs)
{return sqrt(pow(lhs._ix - rhs._ix ,2)+pow(lhs._iy - rhs._iy,2));
}
补充:
前向声明的用处:进行了前向声明的类,可以以引用或指针的形式作为函数的参数,只要不涉及到对该类对象具体成员的访问,编译器可以通过。
(让编译器认识这个类,但是注意如果只进行前向声明,这个类的具体实现没有的话,无法使用这个类的对象,无法创建)
注意:友元的声明要注意和函数的形式完全对应上。
友元类
如上的例子,假设类 Line 中不止有一个 distance 成员函数,还有其他成员函数,它们都需要访问Point 的私有成员,如果还像上面的方式一个一个设置友元,就比较繁琐了,可以直接将 Line 类设置为 Point 的友元类,在工作中这也是更常见的方法。
class Point {//...friend class Line;//...
};
在Point类中声明Line类是本类的友元类,那么Line类中的所有成员函数中都可以访问Point类的私有成员。一次声明,全部解决。
不可否认,友元将类的私有成员暴露出来,在一定程度上破坏了信息隐藏机制,似乎是种“副作用很大的药”,但俗话说“良药苦口”。好工具总是要付出点代价的,拿把锋利的刀砍瓜切菜,总是要注意不要割到手指的。
友元的存在,使得类的接口扩展更为灵活,使用友元进行运算符重载从概念上也更容易理解一些,而且, C++ 规则已经极力地将友元的使用限制在了一定范围内。
友元的特点
-
友元不受类中访问权限的限制——可访问私有成员
-
友元破坏了类的封装性
-
不能滥用友元 ,友元的使用受到限制
-
友元是单向的——A类是B类的友元类,则A类成员函数中可以访问B类私有成员;但并不代表B类是A类的友元类,如果A类中没有声明B类为友元类,此时B类的成员函数中并不能访问A类私有成员
-
友元不具备传递性——A是B的友元类,B是C的友元类,无法推断出A是C的友元类
-
友元不能被继承——因为友元破坏了类的封装性,为了降低影响,设计层面上友元不能被继承