友元
1:友元概念及用处
友元机制允许一个类将对其非公有成员的访问权授予指定的函数和类。友元的声明以关键字friend开始,它只能出现在类定义的内部。因为友元不是授予友元关系的那个类的成员,因此它们不受声明出现部分的访问控制影响,因此友元声明可以出现在类中的任何地方。一般来说,我们通常将友元成组地放在类定义的开始或结尾处。
2:友元声明:
友元可以是普通的非成员函数,或是一个其他类的成员函数或是整个类。如果整个类是友元,那么这个类的所有成员函数均可以授予这个类友元关系的那个类的非公有成员。
(1):类的成员函数作为友元:
如果想要将类B的某一个成员函数f声明为类A的友元,那么我们必须要在定义类A之前先定义类B,其中声明这个成员函数f。接下来定义类A,包括对于函数f的友元声明,最后再定义成员函数f,这时候函数f才可以使用类B的私有成员。
namespace ex{
class A; //类A的前向声明;
class B{
public:
int f(const A&);
};
class A{
friend int B::f(const A&);
private:
int a;
};
/*
int B::f(const A& x)
{
return x.a;
}
*/
}
int ex::B::f(const A& x)
{
return x.a;
}
我们可以注意到,在将类B中的成员函数f()声明为类A的友元时,函数f()前添加了类B作用域限制符,因此我们需要在类A定义之前先定义类B去表明函数f()属于类B。
同时还需要说的是,这时候,B::f()成员函数的定义即可以在命名空间ex作用域内部也可以在命名空间ex的作用域外部,上述两种定义均可。
(2):当我们想要在类A中将整个类B或普通的非成员函数f()设为友元时,我们没必要在类A定义之前声明类B或普通函数f()。我们可以在类A定义之后定义类B或普通函数f(),但是类B或普通函数f()定义所在的作用域应该是包围了类A的最内层的作用域,否则不能调用类A的私有成员。例子代码如下:
namespace ex{
class A{
friend class B;
friend int f(const A&);
private:
int a;
};
//Okay!
class B{
public:
int val(const A& x) { return x.a;}
};
inline int f(const A& x)
{
return x.a;
}
}
//error!
class B{
public:
int val(const ex::A& x) { return x.a;}
};
inline int f(const ex::A& x)
{
return x.a;
}
在上述例子中,包围类A的最内层作用域就是命名空间ex。将类B和普通函数f()的定义放在命名空间ex的里面时,其能调用类A的私有成员,不会出现编译错误。将类B和普通函数f()的定义放在命名空间ex的外面时,就不能正常调用类A的私有成员,会出现编译错误。
3:友元与类继承
像其它类一样,基类或派生类可以使其它类或函数成为友元,友元可以访问类的private和protected的数据。但是需要注意的是,友元关系不能继承,基类的友元对派生类的成员没有特殊访问权限。如果基类被授予了友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。这种规则更有利于维护类的封装性。
4:类模板中的友元声明
在类模板中可以出现如下三种友元声明,每一种都声明了与一个或多个实体友元关系。
1:普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数;
2:类模板或函数模板的友元声明,授予对友元所有实例的访问权;
3:只授予对类模板或函数模板的特定实例的访问权的友元声明。
(1):普通友元:
非模板类或非模板函数可以是类模板的友元:
template<class Type>
class Bar{
friend class FooBar;
friend void fcn();
};
这个声明是说,FooBar的成员和fcn函数都可以访问Bar类的任意实例的private和protected成员。
(2):一般模板友元关系
友元可以是类模板或函数模板
template<class Type>
class Bar{
template<class T> friend class Foo1;
template<class T> friend void temp1_fcn1(const T&);
}
这些友元声明使用与类本身不同的类型形参,该类型形参指的是Fool和temp1_fcn1的类型形参。在这两种情况下,都没有将数目限制的类和函数设为Bar的友元。Foo1的友元声明是说,Foo1的任意实例都可以访问Bar的任意实例的私有元素,类似地,temp_fcn1的任意实例都可以访问Bar的任意实例。
(3):特定的模板友元关系:
除了将一个模板的所有实例设为友元,类也可以只授予对特定实例的访问权。
template<class T>
class Foo2;
template<class T>
void temp1_fcn2(const T&);
template<class Type>
class Bar{
friend class Foo2<char*>;
friend void temp1_fcn2<char*>(char* const&);
};
即使Foo2本身是类模板,友元关系也只能拓展到Foo2的形参类型为char*的特定实例。类似地,temp1_fcn2的友元声明是说,只有形参类型为char*的函数实例是Bar类的友元。形参类型为char*的Foo2和temp1_fcn2的特定实例都可以访问Bar的每个实例。
下面形式的友元声明更为常见:
template<class T>
class Foo3;
template<class T>
void temp1_fcn3(const T&);
template<class Type>
class Bar{
friend class Foo3<Type>;
friend void temp1_fcn3<Type>(const Type&);
};
这些友元定义了Bar的特定实例与使用同一模板实参的Foo3或temp1_fcn3的实例之间的友元关系。每个Bar实例有一个相关的Foo3和temp1_fcn3友元。比如Foo3<int>
可以访问Bar<int>
的私有部分,但不能访问Bar<string>
或者任意其他Bar实例的私有部分。
(4):声明依赖性:
对于情况(1)和情况(2),我们不需要预先声明,但对于情况(3),我们需要预先声明。例子代码如下:
template<class T> class A;
template<class T> class B{
friend class A<T>; //ok: A is known to be a template.
friend class C; //ok: C must be an ordinary nontemplate class;
template<class S>friend class D; //ok: D is a template
friend class E<T>; //error:E wasn't declared as a template;
friend class F<int>; // F wasn't declared as a template
};
如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。