这里整理学习一下C++中的友元函数,学习资料来自C++ primer。
一般而言,我们在类或者中定义了非公有成员,是为了不希望被其他任何外部的类访问,在Java中如果没有暴露任何的set()或者get()方法,那么这个非公有变量一般来说对外是不能被访问到,而非公有方法也是类似如此,当然使用反射就另算了。然而在C++中提供了友元该关键字,提供了一种访问非公有成员的方式。
非成员函数为友元
友元的定义是通过友元关键字类可以允许其他类或者函数访问他的非公有成员。如果一个类想把对应的函数作为友元,则只需要在函数体前面增加friend关键字即可。下面举个例子定义非成员函数为友元,简单先定义一个FriendTest.hpp头文件:
#ifndef FriendTest_hpp#define FriendTest_hppclass FriendTest{ friend int getAdd();private: int add=23; public: FriendTest()=default;};int getAdd();#include#endif /* FriendTest_hpp */
需要注意的是,通过friend关键字仅仅是指定了一个访问权限,并不是函数声明,这点很重要,如果我们希望使用当前类的用户,即FriendTest类,调用某个友元函数,即上方的getAdd()方法,需要在友元声明之外再专门对函数进行一次声明。
为了使友元对类的用户可见,我们通常把友元的声明与本身的类放置在同一个头文件当中,虽然编译器并没有这么要求。
友元函数定义在类的内部,是隐式内联的。
我们直接在FriendTest.cpp中进行实现:
#include "FriendTest.hpp"FriendTest friends;int getAdd(){ return friends.add;}#endif /* FriendVisitor_hpp */
通过如上方式就可以访问到对应的add变量,如果我们去掉了FriendTest class中getAdd()的friend关键字,那么IDE会直接提示我们有错的:
需要注意的是,友元关系是不存在传递性的,如果A有自己的友元B,B也有自己的友元C,那么C是不能访问A中的非公有方法或者变量的。
类为友元
如果我们要把整个类A当做友元来处理的话就需要在类B中将A声明为友元,例子如下,重新定义FriendTest:
#ifndef FriendTest_hpp#define FriendTest_hppclass FriendTest{ friend class FriendVisitor;private: int add=23; int getAdd(){ return add; }public: FriendTest()=default;};#endif /* FriendTest_hpp */#endif /* FriendTest_hpp */
并且声明FriendVisitor类为其友元函数,在看下FriendVisitor.hpp的实现:
//FriendVisitor.hpp#ifndef FriendVisitor_hpp#define FriendVisitor_hpp#include "FriendTest.hpp"#includeclass FriendVisitor{public: int getTestAdd();};#endif /* FriendVisitor_hpp */
再看FriendVisitor.cpp实现:
#ifndef FriendVisitor_hpp#define FriendVisitor_hpp#include "FriendTest.hpp"#includeclass FriendVisitor{ FriendTest test;public: int getTestAdd(){ test.add=44; return test.getAdd(); }};#endif /* FriendVisitor_hpp */
可以看到,通过在FriendTest.hpp声明了FriendVisitor类为其友元类后,在FriendVisitor.hpp的实现中我们可以任意的访问FriendTest的非公有方法以及变量。
成员函数成为友元
上面看到了使用非成员函数以及类当做对于类的友元,除了上述两种还有一种粒度的友元,即作用在成员函数上的友元,还是来看例子,FriendTest.hpp:
#ifndef FriendTest_hpp#define FriendTest_hppclass FriendTest;class FriendVisitor{public: FriendVisitor(){ } int getTestAdd(FriendTest& firendTest);};class FriendTest{ friend int FriendVisitor::getTestAdd(FriendTest&);private: int add=23; int getAdd(){ return add; }public: void test(){ printf("hello"); } FriendTest()=default;};int FriendVisitor::getTestAdd(FriendTest &firendTest){ firendTest.add=434; return firendTest.getAdd();}#endif /* FriendTest_hpp */
通过上述方式即可在FriendVisitor.getTestAdd()中调用FriendTest的非公有函数或者变量。
函数重载与友元
尽管重载函数的名字相同,但是他们仍然是不同的函数,如果一个类想把一组函数声明为他们的友元,他需要对这组函数每一个分别进行声明:
#ifndef FriendTest_hpp#define FriendTest_hppclass FriendTest{ friend int getTestAdd(FriendTest&); friend long getTestAdd(FriendTest&,int);private: int add=23; int getAdd(){ return add; }public: void test(){ printf("hello"); } FriendTest()=default;};int getTestAdd(FriendTest& test){ return test.getAdd();}long getTestAdd(FriendTest& test,int value){ return static_cast(test.getAdd()+value) ;}#endif /* FriendTest_hpp */
友元声明和作用域
类和非成员函数的声明不是必须在他们友元声明之前。但一个名字第一次出现在一个友元声明中2时候,我们隐式假定在当前作用域中是可见的,然而,友元本身不一定真的声明在当前作用域中。就算在类的内部定义该函数,也必须在类的外部提供相对应的函数实现。即,调用友元的函数时候,该友元对应的函数需要被实现。
上述为C++ Primer中的原话,理解起来有点难懂,其实我觉得就是在该文章上面提到的:
通过friend关键字仅仅是指定了一个访问权限,并不是函数声明。函数没有声明的话,编译器该怎么找到函数呢?这么一想就比较简单了。
可以看下书上给的例子助于理解:
struct X{ friend void f(); X(){ f();//错误的,f()没有声明 }; void g(); void h();}void X::g(){ f();//错误,f()还没有声明};void f(){//声明f() };void x::H(){ f();//正确,f()已经被声明了}