15-9 友元类和友元成员函数
友元类
友元类friend class是指能够访问另一个类的私有和受保护成员的类。
以下是一个示例:
#include <iostream>
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{ }
// Make the Display class a friend of Storage
friend class Display;
};
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
// Because Display is a friend of Storage, Display members can access the private members of Storage
void displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
void setDisplayIntFirst(bool b)
{
m_displayIntFirst = b;
}
};
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
display.setDisplayIntFirst(true);
display.displayStorage(storage);
return 0;
}
由于 Display 类是 Storage 的友元,Display 成员可以访问其有权访问的任何 Storage 对象的私有成员。
该程序产生以下结果:

关于友元类的几点补充说明。
首先,尽管Display是Storage的友元类,但Display无法访问Storage对象的this指针(因为this实际上是函数参数)。
其次,友元关系并非互惠。Display是Storage的友元类,并不意味着Storage也是Display的友元类。若要使两个类成为彼此的友元类,双方都必须声明对方为友元类。
作者注:
若此文触及敏感话题,敬请谅解!
类友关系同样不具传递性。若类A是B的友类,而B又是C的友类,则不能推断A是C的友类。
进阶说明
友关系亦不可继承。当类A将B设为友类时,B的派生类不会自动成为A的友类。
声明友元类时,该声明同时充当被声明类的向前声明。这意味着我们无需在声明友元关系前预先声明被声明类。在上例中,友元类Display既是Display类的向前声明,也是友元声明。
友元成员函数
无需将整个类设为友元,仅需将单个成员函数设为友元即可。其实现方式与将非成员函数设为友元类似,区别在于使用成员函数的名称。
然而实际操作中,这可能比预期更复杂。让我们将前例转换为将 Display::displayStorage 设为友元成员函数。你可能会尝试如下写法:
#include <iostream>
class Display; // forward declaration for class Display
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{
}
// Make the Display::displayStorage member function a friend of the Storage class
friend void Display::displayStorage(const Storage& storage); // error: Storage hasn't seen the full definition of class Display
};
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
void displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
return 0;
}


然而事实证明这行不通。要使单个成员函数成为友元函数,编译器必须看到该友元成员函数所属类的完整定义(而不仅仅是前向声明)。由于类Storage尚未看到类Display的完整定义,当我们尝试将成员函数设为友元时,编译器就会报错。
所幸这个问题很容易解决:只需将类Display的定义移至类Storage之前(可在同一文件中实现,或将Display的定义移至头文件,并在定义Storage之前#include该头文件)。
#include <iostream>
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
void displayStorage(const Storage& storage) // compile error: compiler doesn't know what a Storage is
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{
}
// Make the Display::displayStorage member function a friend of the Storage class
friend void Display::displayStorage(const Storage& storage); // okay now
};
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
return 0;
}

但我的此处还有个错误, 并未解决, 使用了前向声明
class Stronge;后才消除。
然而,我们现在又遇到另一个问题。由于成员函数 Display::displayStorage() 使用 Storage 作为引用参数,而我们刚刚将 Storage 的定义移到了 Display 定义之后,编译器会报错说它不知道 Storage 是指什么。我们不能通过重新排列定义顺序来修复这个问题,因为那样会使我们之前的修复失效。
所幸这个问题同样能通过几个简单步骤解决。首先,我们可以为类Storage添加前向声明,这样编译器在看到类完整定义之前就能接受对Storage的引用。
其次,我们可以将Display::displayStorage()的定义移出类体,置于Storage类的完整定义之后。
具体实现如下:
#include <iostream>
class Storage; // forward declaration for class Storage
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
void displayStorage(const Storage& storage); // forward declaration for Storage needed for reference here
};
class Storage // full definition of Storage class
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{
}
// Make the Display::displayStorage member function a friend of the Storage class
// Requires seeing the full definition of class Display (as displayStorage is a member)
friend void Display::displayStorage(const Storage& storage);
};
// Now we can define Display::displayStorage
// Requires seeing the full definition of class Storage (as we access Storage members)
void Display::displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
return 0;
}

现在一切都能正确编译:类Storage的前向声明足以满足Display类内部对Display::displayStorage()的声明。Display类的完整定义满足将Display::displayStorage()声明为Storage的友函数的要求。而类Storage的完整定义则足以满足成员函数Display::displayStorage()的定义。
若仍感困惑,请参阅上方程序中的注释。关键点在于:类的前向声明可满足对该类的引用,但访问类成员时,编译器必须已见过完整的类定义。
若觉得这很麻烦——确实如此。所幸这种操作仅因我们试图将所有内容置于单个文件中才必要。更优解是将每个类定义放在独立的头文件中,成员函数定义则放在对应的.cpp文件里。如此一来,所有类定义都可在.cpp文件中使用,无需重新排列类或函数!
测验时间
问题 #1
在几何学中,点是空间中的一个位置。我们可以将三维空间中的点定义为坐标 x、y 和 z 的集合。例如,点 { 2.0, 1.0, 0.0 } 就是坐标空间中 x=2.0、y=1.0、z=0.0 的点。
在物理学中,向量是具有大小(长度)和方向(但无位置)的量。我们可将三维空间中的向量定义为x、y、z三个值,分别表示向量沿x、y、z轴的方向(长度可由此推导)。例如,向量 { 2.0, 0.0, 0.0 } 表示沿正x轴方向(仅限该轴)且长度为2.0的向量。
向量可作用于点对象使其移动至新位置。具体实现方式是将向量方向与点坐标相加,从而得到新坐标。例如,Point { 2.0, 1.0, 0.0 } + Vector { 2.0, 0.0, 0.0 } 将得到 Point { 4.0, 1.0, 0.0 }。
此类点与向量常用于计算机图形学(点代表形状的顶点,向量表示形状的运动)。
给定以下程序:
#include <iostream>
class Vector3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Vector3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{
}
void print() const
{
std::cout << "Vector(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
};
class Point3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Point3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{ }
void print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
void moveByVector(const Vector3d& v)
{
// implement this function as a friend of class Vector3d
}
};
int main()
{
Point3d p { 1.0, 2.0, 3.0 };
Vector3d v { 2.0, 2.0, -3.0 };
p.print();
p.moveByVector(v);
p.print();
return 0;
}

步骤 #1
将 Point3d 设为 Vector3d 的友元类,并实现函数 Point3d::moveByVector()。

显示解决方案
#include <iostream>
class Vector3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Vector3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{
}
void print() const
{
std::cout << "Vector(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
friend class Point3d; // Point3d is now a friend of class Vector3d
};
class Point3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Point3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{
}
void print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
void moveByVector(const Vector3d& v)
{
m_x += v.m_x;
m_y += v.m_y;
m_z += v.m_z;
}
};
int main()
{
Point3d p { 1.0, 2.0, 3.0 };
Vector3d v { 2.0, 2.0, -3.0 };
p.print();
p.moveByVector(v);
p.print();
return 0;
}
步骤 #2
不将 Point3d 类设为 Vector3d 的友元类,而是将成员函数 Point3d::moveByVector 设为 Vector3d 的友元函数。

显示解决方案
#include <iostream>
class Vector3d; // first, we need to tell the compiler that a class named Vector3d exists
class Point3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Point3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{
}
void print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
void moveByVector(const Vector3d& v); // so we can use Vector3d here
// note: we can't define this function here, because Vector3d hasn't been defined yet (just forward declared)
};
class Vector3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Vector3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{
}
void print() const
{
std::cout << "Vector(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
friend void Point3d::moveByVector(const Vector3d& v); // Point3d::moveByVector() is now a friend of class Vector3d
};
// Now that Vector3d has been defined, we can define the function Point3d::moveByVector()
void Point3d::moveByVector(const Vector3d& v)
{
m_x += v.m_x;
m_y += v.m_y;
m_z += v.m_z;
}
int main()
{
Point3d p { 1.0, 2.0, 3.0 };
Vector3d v { 2.0, 2.0, -3.0 };
p.print();
p.moveByVector(v);
p.print();
return 0;
}
步骤 #3
使用 5 个独立文件重构前一步的解决方案:Point3d.h、Point3d.cpp、Vector3d.h、Vector3d.cpp 和 main.cpp。

感谢读者 Shiva 提供的建议和解决方案。
显示解决方案
Point3d.h
// Header file that defines the Point3d class
#ifndef POINT3D_H
#define POINT3D_H
class Vector3d; // forward declaration for class Vector3d for function moveByVector()
class Point3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Point3d(double x, double y, double z);
void print() const;
void moveByVector(const Vector3d& v); // forward declaration above needed for this line
};
#endif
Point3d.cpp:
// Member functions of the Point3d class defined here
#include "Point3d.h" // Point3d class defined here
#include "Vector3d.h" // for the parameter of the function moveByVector()
#include <iostream>
Point3d::Point3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{}
void Point3d::moveByVector(const Vector3d& v)
{
// Add the vector components to the corresponding point coordinates
m_x += v.m_x;
m_y += v.m_y;
m_z += v.m_z;
}
void Point3d::print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
Vector3d.h
// Header file that defines the Vector3d class
#ifndef VECTOR3D_H
#define VECTOR3D_H
#include "Point3d.h" // for declaring Point3d::moveByVector() as a friend
class Vector3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Vector3d(double x, double y, double z);
void print() const;
friend void Point3d::moveByVector(const Vector3d& v);
};
#endif
Vector.cpp
// Member functions of the Vector3d class defined here
#include "Vector3d.h" // Vector3d class defined in this file
#include <iostream>
Vector3d::Vector3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{}
void Vector3d::print() const
{
std::cout << "Vector(" << m_x << " , " << m_y << " , " << m_z << ")\n";
}
main.cpp
#include "Vector3d.h"
#include "Point3d.h"
int main()
{
Point3d p { 1.0, 2.0, 3.0 };
Vector3d v { 2.0, 2.0, -3.0 };
p.print();
p.moveByVector(v);
p.print();
return 0;
}
这个自己没独立搞出来,呜呜呜, 参考了答案之后运行如下:




浙公网安备 33010602011771号