在上一课中,我们将 operator+
实现为友元函数:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents{ cents }
{}
// 使用友元函数实现 Cents + Cents
friend Cents operator+(const Cents& c1, const Cents& c2);
int getCents() const { return m_cents; }
};
// 注意:此函数并非成员函数!
Cents operator+(const Cents& c1, const Cents& c2)
{
// 利用 Cents 构造函数以及内建的 operator+(int, int)
// 由于是友元函数,可直接访问 m_cents
return { c1.m_cents + c2.m_cents };
}
int main()
{
Cents cents1{ 6 };
Cents cents2{ 8 };
Cents centsSum{ cents1 + cents2 };
std::cout << "I have " << centsSum.getCents() << " cents.\n";
return 0;
}
使用友元函数重载运算符十分便利,因为它能直接访问参与运算的类的内部成员。在上面的示例中,友元函数 operator+
直接使用了 m_cents
。
然而,如果无需直接访问私有成员,就可以将重载运算符写成普通函数。上述 Cents
类已提供了访问函数 getCents()
,使得我们可以在不访问私有成员的情况下完成运算。因此,可以把 operator+
写成非友元:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents{ cents }
{}
int getCents() const { return m_cents; }
};
// 注意:此函数既非成员函数,也非友元函数!
Cents operator+(const Cents& c1, const Cents& c2)
{
// 利用 Cents 构造函数以及内建的 operator+(int, int)
// 无需直接访问私有成员
return Cents{ c1.getCents() + c2.getCents() };
}
int main()
{
Cents cents1{ 6 };
Cents cents2{ 8 };
Cents centsSum{ cents1 + cents2 };
std::cout << "I have " << centsSum.getCents() << " cents.\n";
return 0;
}
由于普通函数与友元函数在行为上几乎相同(区别仅在于对私有成员的访问权限),通常我们不会刻意区分二者。唯一需要注意的是:友元函数在类内的声明同时充当了原型;而普通函数版本则必须在别处显式提供原型。
示例文件结构如下:
Cents.h
#ifndef CENTS_H
#define CENTS_H
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents{ cents }
{}
int getCents() const { return m_cents; }
};
// 需要显式提供 operator+ 的原型,以便其他文件识别该重载
Cents operator+(const Cents& c1, const Cents& c2);
#endif
Cents.cpp
#include "Cents.h"
// 注意:此函数既非成员函数,也非友元函数!
Cents operator+(const Cents& c1, const Cents& c2)
{
// 利用 Cents 构造函数以及内建的 operator+(int, int)
// 无需直接访问私有成员
return { c1.getCents() + c2.getCents() };
}
main.cpp
#include "Cents.h"
#include <iostream>
int main()
{
Cents cents1{ 6 };
Cents cents2{ 8 };
Cents centsSum{ cents1 + cents2 }; // 若无 Cents.h 中的原型,此处将编译失败
std::cout << "I have " << centsSum.getCents() << " cents.\n";
return 0;
}
最佳实践
若现有成员函数已能提供足够信息,应优先使用普通函数而非友元函数来重载运算符(越少函数触及类内部越好)。但不要为了把运算符重载为普通函数而额外增加访问函数!
最佳实践
如果无需额外函数即可用普通函数完成运算符重载,则优先采用普通函数而非友元函数。