为什么建议在运算符重载中将函数声明为“朋友”

时间:2014-12-01 09:34:54

标签: c++ c++11

我在许多地方都读过这篇文章,建议使用"朋友"虽然过载运营商但没有机构明确解释为什么真的需要它?为什么我们不能将它们声明为普通成员函数?有什么缺点吗?

用Google搜索,但没有得到任何明确答案。

7 个答案:

答案 0 :(得分:3)

有时,您无法将运算符重载声明为成员函数,例如IO operator<<operator>>。这些函数的第一个参数必须是ostreamistream,它们是库类,您无法扩展它们,声明friend等函数可以访问私有变量你的班级。

答案 1 :(得分:1)

使用friend表示它是非会员朋友的功能。

为了通过最小化依赖性来改进封装,最好声明非成员非友元函数。如果需要访问该类的私有/受保护成员,请将其设为friend。最后,使其成为一个成员函数。

这是一个确定函数是否应该是成员和/或朋友的算法,来自[C ++编码标准:101规则,指南和最佳实践作者:Herb Sutter,Andrei Alexandrescu](第44项。更喜欢编写非成员非友情函数):

// If you have no choice then you have no choice; make it a member if it must be:

If the function is one of the operators =, ->, [], or (), which must be members:

    Make it a member.

// If it can be a nonmember nonfriend, or benefits from being a nonmember friend, do it:

Else if: a) the function needs a different type as its left-hand argument (as do operators >> or <<, for example); or b) it needs type conversions on its leftmost argument; or c) it can be implemented using the class's public interface alone:

    Make it a nonmember (and friend if needed in cases a) and b) ).

    If it needs to behave virtually:

        Add a virtual member function to provide the virtual behavior, and implement the nonmember in terms of that.

Else: Make it a member.

在某些情况下,例如上面提到的a)和b),你不能通过成员函数来实现它们,你必须将它们声明为非成员函数,如果需要访问它们,则将它们设为friend私人/受保护的班级成员。

答案 2 :(得分:1)

人们使用friend的原因有几个:

  • 有时授予友谊实际上是合理的,因为公共API不应该暴露某些成员而不是需要比较

  • 方便懒惰的程序员授予对所有privateprotected数据成员的访问权限,确保您可以编写运算符实现而无需返回授权以后访问或使用不太明显/直接的公共功能(这不是一个好的理由,只是一个懒惰的)

  • 您可以在类中定义运算符函数,其中任何模板参数,typedef,常量等都不需要像在周围[namespace]范围中那样显式限定。对于那些刚接触C ++的人来说,这简单得多。

e.g:

    template <typename T>
    struct X
    {
        friend bool operator==(const X& lhs, const X& rhs) { ... }
    };

... ... VS        ...结构X如上所述,没有== ...

    template <typename T>
    bool operator==(const X<T>& lhs, const X<T>& rhs) { ... }
  • 在一个两刀一石的铲子中,它使该功能名义上内联,避免了一个定义规则的并发症

上面只有第一个 原因是让操作员成为朋友的一个令人信服的功能性原因,而不是使其成为非成员函数,因为封装较少且相应地涉及更高的维护负担。

有很好的理由但是 更喜欢朋友或非朋友的非成员函数 ,因为隐式构造函数可以启用以允许运算符使用类的一个实例和另一个可以构造第二个实例的值:

struct X { X(int); };
bool operator==(const X& lhs, const X& rhs);

x == 3;   // ok for member or non-member operator==
3 == x;   // only works for non-member operator== after implicit X(3) for lhs

答案 3 :(得分:0)

您只列出了重载运算符的两个选项,实际上有三个选项:

  • 全球职能,而不是朋友
  • 会员功能
  • 全球职能,班上的朋友

你没有列出第一个,但它是推荐的。如果可以根据现有的类公共接口定义运算符,请将其定义为类外的全局函数。这样就不需要扩展类公共接口,最大限度地减少了访问类私有成员的函数数量。

如果运营商需要访问班级私人会员,该怎么办?然后你有两个选项 - 成员函数或朋友全局函数。从这两个成员函数是优选的,因为它更清洁。但是,在某些情况下,无法将重载运算符定义为成员函数。如果你的类的对象是双参数运算符的右手参数,那么全局函数是唯一的选择。

答案 4 :(得分:0)

我假设我们正在比较课堂上定义的全球范围的朋友和非朋友 前者有时是首选的原因是这些功能......

  • ...需要访问数据成员才能执行操作。这也可以通过通过public getter提供数据来放松封装来完成,但并不总是如此。

  • ... 只能通过ADL 找到,以避免污染某些名称空间和过载候选集。 (只有friend个类中定义的函数friend满足此要求!)

此外,一个小问题是,对于类模板,由于我们避免使函数成为模板,因此更容易定义对其中的特化进行操作的全局函数。该函数也隐式inline。所有这些都缩短了代码,但不是主要原因。

答案 5 :(得分:0)

如果您的实现遵循正确的数据封装,那么您可能不会将您的数据变量暴露给外部世界,并且所有数据成员都将被声明为私有。

但是大多数情况下使用运算符重载您将访问数据成员,请在此处注意您将访问类外的数据成员。因此,为了提供对类外部数据成员的访问,建议将操作符重载函数声明为朋友。

但是对于一元运算符可能不需要这样,因为它将对调用它的特定类的数据成员进行操作。

如果您需要任何示例供您理解,请告诉我。

答案 6 :(得分:0)

在处理操作员时,非成员函数有很大的优势。

大多数运算符都是二进制的(采用两个参数)并且有点对称,并且只有*this是左侧参数时才使用成员运算符。所以你需要使用非成员运算符。

朋友模式既可以让操作员完全访问该类(并且操作员通常足够亲密,这不是有害的),并且它使其在ADL之外不可见。此外,如果它是template类,则有很大的优势。

ADL之外的隐形可以让你做出像这样的疯狂事情:

struct bob {
  template<class Lhs, class Rhs>
  friend bob operator+( Lhs&&, Rhs&& ) { /* implementation */ }
}

此处我们的operator+template,看似匹配任何内容。除非因为它只能通过bob上的ADL找到,否则只有在至少一个bob对象上使用它才会匹配。这种技术可以让你了解rvalue / lvalue重载,对类型属性进行SFINAE测试等。

template类型的另一个优点是运算符最终不是template函数。 look here

template<class T>
struct X {};

template<class T>
bool operator==( X<T>, X<T> ) { return true; }

template<class T>
struct Y {
  friend bool operator==( Y, Y ) { return true; }
};

struct A {
  template<class T>
  operator X<T>() const { return {}; }
};

struct B {
  template<class T>
  operator Y<T>() const { return {}; }
};


int main() {
  A a;
  X<int> x;
  B b;
  Y<int> y;
  b == y; // <-- works!
  a == x; // <-- fails to compile!
}

X<T>有一个template operator==,而Y<T>有一个friend operator==template版本必须将两个参数模式匹配为X<T>,否则它将失败。因此,当我将X<T>和类型转换为X<T>时,它无法编译,因为模式匹配不会执行用户定义的转换。

另一方面,Y<T>&#39; operator==不是template函数。因此,当调用b == y时,会找到它(通过y上的ADL),然后测试b是否可以转换为y(它可以是) ,呼叫成功。

具有模式匹配的

template运算符是脆弱的。您可以在标准库中看到此问题,其中运算符过载的几个方面会阻止转换工作。如果运营商被宣布为friend operator而不是公共免费template运营商,则可以避免此问题。