友元类

本文详细介绍了C++中友元类与友元函数的概念及其使用方法,包括如何声明友元类、友元成员函数及解决循环依赖问题的技巧。

友元类

声明友元类:
class class_a
{friend class class_b;
 ... };


声明位置可以位于公有、私有和保护部分,其所在的位置无关紧要。

 

友元类(class_b)的所有方法都可以访问原始类(class_a)的私有成员和保护成员。

 

   由于class_b类提到了class_a类,所以编译器必须了解class_a类后,才能处理class_b类,为此,最简单的方法是首先定义class_a类。另一种方法是,使用前向声明(forward declaration)。

 

友元成员函数

 

让特定的类成员成为另一个类的友元:
(必须小心排列各种声明和定义的顺序)
class class_a
{friend void class_b::set(class_a & A,int c);
 ... };

 

   要使编译器能够处理这条语句,它必须知道class_b的定义。这意味着应将class_b的定义放到class_a的定义前面。但class_b的方法提到了class_a对象,而这又意味着class_a定义应当位于class_b定义之前。避开这种循环依赖的方法是,使用前向声明(forward declaration)。为此,需要在class_b定义的前面插入下面的语句:
class class_a;           //forward declaration
class class_b {...};
class class_a {...};

 

假如class_b声明包含了内联代码调用class_a方法:
void show(class_a & A) {A.show();}

 

   由于这将调用class_a的一个方法,所以编译器此时必须已经看到了class_a类的声明,这样才能知道class_a有哪些方法,但class_a的声明现在却位于class_b声明的后面。解决方法是,类中只包含方法声明,并将实际的定义放在class_a类之后:
class class_a;          //forward declaration
class class_b {...};  //void show(class_a & A);
class class_a {...};
//put class_b method definitions here
inline void class_b::show(class_a & A){A.show();}
(通过在方法定义中使用inline关键字,仍然可以使其成为内联方法)

 

   内联函数的链接性是内部的,这意味着函数定义必须在使用函数的文件中,或者位于头文件中,并在使用函数的文件中包含该头文件。

 

其他友好关系

   相互友元成员函数,即类class_a的一些方法作为类class_b的友元,同时类class_b的一些方法也作为类class_a的友元。对于使用class_b对象的class_a方法,其原型可在class_b类声明之前声明,但必须在class_b类声明之后定义,以便编译器有足够的信息来编译该方法:

class class_a
{ friend class class_b;
  public: void use(class_b & B);
          void show()const;
  ... };

 

class class_b
{ friend class class_a;
  public: void set(class_a & A) {A.show();}
  ... };

 

inline void class_a::use(class_b & B) { ... }

 

   由于class_b的声明位于class_a声明的后面,所以可以在类声明中定义class_b::set(),但class_a::use()方法必须在class_a声明的外部定义,使其位于class_b声明的后面。

 

   共同的友元,即当函数需要访问两个类的私有数据时。它可以是一个类的成员,同时是另一个类的友元。不过有时将函数作为两个类的友元更合理:

class class_a;        //forward declaration
class class_b
{ friend void sync(class_a & A,const class_b & B);  //sync A to B
  friend void sync(class_b & B,const class_a & A);  //sync B to A
  ...};


class class_a
{ friend void sync(class_a & A,const class_b & B);  //sync A to B
  friend void sync(class_b & B,const class_a & A);  //sync B to A
  ...};


// define the friend functions
inline void sync(class_a & A,const class_b & B) {...}
inline void sync(class_b & B,const class_a & A) {...}

 


(向前声明使编译器看到class_b类声明中的友元声明时,知道class_a是一种类型)

<think>我们正在讨论C++中的友元(friend class)。根据引用内容,友元允许一个(称为友元)访问另一个的私有(private)和保护(protected)成员。这是C++中打破封装性的一种机制,需要谨慎使用。 ### 友元的基本概念 - **定义**:如果A将B声明为友元(即在A的定义中使用`friend class B;`),那么B的所有成员函数都可以访问A的私有成员和保护成员。 - **特性**: 1. **单向性**:友元关系是单向的。如果A声明B是它的友元,这并不意味着A可以访问B的私有成员(除非B也声明A为友元)。 2. **非传递性**:如果A是B的友元B是C的友元,这并不意味着A是C的友元。 3. **非继承性**:友元关系不能被继承。如果A是B的友元C继承自A,那么C不是B的友元(除非B显式声明C为友元)。 ### 友元的使用方法 在声明友元时,通常将`friend`关键字和友元名放在的定义中。例如: ```cpp class A { private: int private_data; public: // 声明B是A的友元 friend class B; }; class B { public: void accessA(A& a) { // 由于B是A的友元,可以直接访问A的私有成员 std::cout << a.private_data << std::endl; } }; ``` ### 示例代码分析 引用[3]提供了一个引擎(Engine)和汽车(Car)的示例: ```cpp #include <iostream> using namespace std; class Engine { private: int horsepower; public: Engine(int hp) : horsepower(hp) {} // 声明Car为友元 friend class Car; }; class Car { public: void showEnginePower(const Engine& e) { // 由于Car是Engine的友元,可以直接访问Engine的私有成员horsepower std::cout << "Engine horsepower: " << e.horsepower << std::endl; } }; int main() { Engine engine(500); Car car; car.showEnginePower(engine); // 输出:Engine horsepower: 500 return 0; } ``` 在这个例子中: - `Engine`将`Car`声明为友元,因此`Car`的成员函数`showEnginePower`可以访问`Engine`的私有成员`horsepower`。 ### 使用注意事项 1. **破坏封装**:友元打破了的封装性,因此应该只在必要的情况下使用,例如当两个在逻辑上紧密耦合,且需要频繁访问对方私有成员时。 2. **替代方案**:在可能的情况下,优先考虑使用公有成员函数(getter/setter)来访问私有数据,以保持封装性。 3. **友元声明位置**:友元声明可以在的任何部分(private、protected或public)出现,因为友元声明不受访问控制符的影响。但通常放在定义的开始或结束位置以保持清晰。 ### 模板友元 引用[4]中提到了模板友元的情况。当友元是一个模板时,需要在友元声明中指定模板参数。例如: ```cpp template<typename T> class A { private: T data; public: // 声明模板B为友元 template<typename U> friend class B; }; // 这样,对于任何型U,B<U>都是A<T>的友元,但注意:通常我们可能需要更精确的控制。 ``` 但是,上述声明意味着对于每一个特化的`A<T>`,所有特化的`B<U>`都是它的友元。如果我们希望只有相同型的`B<T>`才是`A<T>`的友元,可以这样声明: ```cpp template<typename T> class B; // 前置声明 template<typename T> class A { private: T data; public: // 声明特定特化的B<T>为友元 friend class B<T>; }; ``` ### 总结 友元提供了一种访问其他私有成员的机制,但应谨慎使用。在需要两个紧密协作的情况下,友元可以避免使用公共接口带来的性能开销或设计复杂性。然而,过度使用友元会破坏封装,增加之间的耦合度。 §§相关问题§§ 1. 友元函数和友元有什么区别? 2. 在C++中,如何声明一个模板作为友元? 3. 使用友元可能会带来哪些设计上的问题? 4. 除了友元,还有哪些方法可以访问的私有成员?</think>### C++ 友元详解 友元(friend class)是 C++ 中允许特定访问另一个私有成员的机制。通过 `friend` 关键字声明,它打破了严格的封装性,在特定场景下可提升代码效率和灵活性。 #### 一、核心特性 1. **单向访问权限** 若 A 声明 B 为友元,则 B 可访问 A 的私有成员,但 A **不能**访问 B 的私有成员。 示例: ```cpp class Storage { friend class Display; // 声明友元 private: int secret = 42; }; class Display { public: void show(const Storage& s) { std::cout << s.secret; // 允许访问私有成员 } }; ``` 2. **非传递性** 若 A 是 B 的友元,B 是 C 的友元,**不等于** A 是 C 的友元。 3. **非继承性** 派生**不继承**基友元关系。若基声明友元,派生仍需单独声明。 #### 二、使用场景 1. **紧密协作的** 如引擎(Engine)和汽车(Car): ```cpp class Engine { friend class Car; // 声明友元 private: int horsepower; public: Engine(int hp) : horsepower(hp) {} }; class Car { public: void showPower(const Engine& e) { std::cout << "Power: " << e.horsepower; // 直接访问私有成员 } }; ``` [^3] 2. **工具辅助** 如日志(Logger)访问多个的内部状态: ```cpp class Network { friend class Logger; private: int packetCount; }; class Logger { public: static void log(const Network& net) { std::cout << "Packets: " << net.packetCount; } }; ``` #### 三、模板友元 当涉及模板时,需前置声明并指定模板参数: ```cpp template<typename T> class Box { friend class Inspector<T>; // 声明模板友元 private: T content; }; template<typename T> class Inspector { public: void check(const Box<T>& box) { std::cout << "Content: " << box.content; } }; ``` [^4] #### 四、注意事项 1. **谨慎使用** 过度使用会破坏封装性,增加耦合度。优先考虑公有接口(getter/setter)。 2. **声明位置无关** `friend` 声明可放在的任何区域(public/private/protected),效果相同。 3. **替代方案** 考虑使用组合(composition)或中介模式(mediator pattern)替代友元。 > **最佳实践**:仅当两个在逻辑上高度内聚(如引擎与汽车),且频繁访问私有数据时使用友元。其他场景建议保持封装[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值