32

赋值运算符可以使用成员函数重载,但不能使用非成员friend函数:

class Test
{
    int a;
public:
    Test(int x)
        :a(x)
    {}
    friend Test& operator=(Test &obj1, Test &obj2);
};

Test& operator=(Test &obj1, Test &obj2)//Not implemented fully. just for test.
{
    return obj1;
}

它会导致此错误:

错误 C2801:'operator =' 必须是非静态成员

为什么不能使用friend函数来重载赋值运算符?编译器允许重载其他运算符,例如+=-=using friend。支持的内在问题/限制是operator=什么?

4

9 回答 9

38

首先需要注意的是,这与具体实现为友元的算子无关。它实际上是将复制分配实现为成员函数或非成员(独立)函数。该独立函数是否会成为朋友完全无关紧要:它可能是,也可能不是,取决于它想在类中访问什么。

现在,这个问题的答案在 D&E 书(The Design and Evolution of C++)中给出。这样做的原因是编译器总是为类声明/定义成员复制赋值运算符(如果您不声明自己的成员复制赋值运算符)。

如果该语言还允许将复制赋值运算符声明为独立(非成员)函数,则您可能会得到以下结果

// Class definition
class SomeClass {
  // No copy-assignment operator declared here
  // so the compiler declares its own implicitly
  ...
};

SomeClass a, b;

void foo() {
  a = b;
  // The code here will use the compiler-declared copy-assignment for `SomeClass`
  // because it doesn't know anything about any other copy-assignment operators
}

// Your standalone assignment operator
SomeClass& operator =(SomeClass& lhs, const SomeClass& rhs);

void bar() {
  a = b;
  // The code here will use your standalone copy-assigment for `SomeClass`
  // and not the compiler-declared one 
}

如上例所示,复制赋值的语义会在翻译单元的中间发生变化——在声明独立运算符之前,使用编译器的版本。声明后使用您的版本。程序的行为将根据您将独立复制赋值运算符的声明放在哪里而改变。

这被认为是不可接受的危险(确实如此),因此 C++ 不允许将复制赋值运算符声明为独立函数。

确实,在您的特定示例中,恰好专门使用了朋友函数,运算符在类定义中很早就声明了(因为这是声明朋友的方式)。因此,在您的情况下,编译器当然会立即知道您的运算符的存在。但是,从 C++ 语言的角度来看,一般问题与友元函数没有任何关系。从 C++ 语言的角度来看,它是关于成员函数与非成员函数的,由于上述原因,完全禁止复制赋值的非成员重载。

于 2010-10-14T13:51:53.463 回答
31

因为编译器提供的默认值operator=(按成员复制的)总是优先的。即你的朋友operator=永远不会被调用。

编辑:这个答案正在回答

支持 = 运算符的固有问题/限制是什么?

问题的一部分。此处的其他答案引用了标准中说您不能这样做的部分,但这很可能是标准的该部分以这种方式编写的原因

于 2010-10-14T13:36:02.157 回答
8

$13.5.3 - “赋值运算符应由只有一个参数的非静态成员函数实现。因为如果用户未声明复制赋值运算符 operator=,则为类隐式声明(12.8),基类赋值运算符始终被派生类的复制赋值运算符隐藏。”

于 2010-10-14T13:40:00.817 回答
7

因为有些操作员必须是成员。这些运算符是:
operator[]
operator=
operator()
operator->

和类型转换运算符,例如operator int.

尽管人们可能能够解释为什么 operator = 必须是成员,但他们的论点不能适用于列表中的其他人,这让我相信“为什么”的答案是“只是因为”。

高温高压

于 2010-10-14T13:38:57.613 回答
3

operator=是一个特殊的成员函数,如果你不自己声明,编译器会提供它。由于它的这种特殊状态是operator=有意义的 ro 要求它是一个成员函数,因此不可能同时存在编译器生成的成员operator=和用户声明的朋友operator=,并且不可能在两者之间进行选择。

于 2010-10-14T13:42:06.243 回答
1

为什么不能使用友元函数重载赋值运算符?

简短的回答:只是因为.

稍微长一点的答案:这就是固定语法的方式。一些运算符必须是成员函数。赋值运算符是其中之一,

于 2010-10-14T13:40:57.780 回答
0

的意图operator=是对当前对象的赋值操作。那么 LHS 或左值是同一类型的对象。

考虑 LHS 是整数或其他类型的情况。那是由operator int()或相应的operator T()功能处理的情况。因此 LHS 的类型已经定义,但非成员operator=函数可能会违反这一点。

因此,它被避免。

于 2012-07-04T13:16:16.570 回答
0

这篇文章适用于 C++11

为什么有人想要非会员operator=?好吧,有了一个成员operator=,下面的代码是可能的:

Test const &ref = ( Test() = something ); 

这会创建一个悬空引用。非成员运营商将解决此问题:

Test& operator=(Test &obj1, Test obj2)

因为现在prvalueTest()将无法绑定到obj1. 事实上,这个签名将强制我们永远不会返回一个悬空引用(当然,除非我们提供了一个) - 该函数总是返回一个“有效”的左值,因为它强制使用左值调用。

然而,在 C++11 中,现在有一种方法可以指定只能在左值上调用成员函数,因此您可以通过编写成员函数来实现相同的目标:

Test &operator=(Test obj2) &
//                        ^^^

现在上面带有悬空引用的代码将无法编译。


注意。operator=应该通过 value 或 const 引用取右侧。在实现复制和交换习语时,按值取值很有用,这是一种轻松编写安全(但不一定是最快的)复制赋值和移动赋值运算符的技术。

于 2015-02-19T03:59:12.483 回答
0

因为在类中已经有一个隐式的运算符重载函数'='来进行浅拷贝。因此,即使您使用友元函数重载,您也永远无法调用它,因为我们进行的任何调用都会调用隐式浅拷贝方法而不是重载的友元函数。

于 2015-05-28T04:35:58.257 回答