operator << 必须只接受一个参数。

107
#include "logic.h"
...

class A
{
friend ostream& operator<<(ostream&, A&);
...
};

logic.cpp

#include "a.h"
...
ostream& logic::operator<<(ostream& os, A& a)
{
...
}
...

编译时,它显示:

"std::ostream& logic::operator<<(std::ostream&, A&)" 必须只接收一个参数。

问题出在哪里?

6个回答

153

问题在于你将其定义在类内,这意味着:

a) 第二个参数是隐式的(this);

b) 它不会达到你想要继承 std::ostream 的目的。

你必须将其定义为自由函数:

class A { /* ... */ };
std::ostream& operator<<(std::ostream&, const A& a);

10
他将其声明为友元函数,并将其定义为成员函数。 - asaelr
2
如 https://en.cppreference.com/w/cpp/language/operators 所述,“以 std::istream& 或 std::ostream& 作为左操作数的 operator>> 和 operator<< 的重载被称为插入和提取运算符。由于它们将用户定义类型作为右操作数(a@b 中的 b),因此必须实现为非成员函数”。 - Morteza
1
如果我们需要打印类A的任何私有成员,则应将operator<<实现为类A中的友元函数。因此,在这种情况下,就像@asaelr提到的那样,当定义友元函数时,您不使用类的名称来限定友元函数的名称。 - Rishit Chaudhary
1
不知道为什么这个问题会被高票。在类内声明的friend函数是一个自由函数,属于封闭命名空间的成员,而不是类的非静态成员函数。所以a)是错误的。我也不明白b)试图表达什么意思。问题中唯一错误的地方是定义中的logic::部分。 - user17732522
我也认为这需要一些修正。由于这个回答得到了很多赞同,对于新学习者来说,纠正这个回答是很有必要的。就像上面@user17732522所评论的那样。 - starriet

57

友元函数不是成员函数,因此问题在于您将operator<<声明为A的友元函数:

 friend ostream& operator<<(ostream&, A&);

然后尝试将其定义为类 logic 的成员函数

 ostream& logic::operator<<(ostream& os, A& a)
          ^^^^^^^

你是否对 logic 是一个类还是一个命名空间感到困惑?

错误的原因是你试图定义一个成员 operator<<,它需要两个参数,这意味着它需要包括隐式的 this 参数在内的三个参数。但是运算符只能有两个参数,所以当你写下 a << b 时,两个参数分别为 ab

你需要将 ostream& operator<<(ostream&, const A&) 定义为成员函数,绝不要将其定义为 logic 的成员,因为它与该类无关!

std::ostream& operator<<(std::ostream& os, const A& a)
{
  return os << a.number;
}

3

我在使用模板类时遇到了这个问题。这里是我不得不使用的更通用的解决方案:

template class <T>
class myClass
{
    int myField;

    // Helper function accessing my fields
    void toString(std::ostream&) const;

    // Friend means operator<< can use private variables
    // It needs to be declared as a template, but T is taken
    template <class U>
    friend std::ostream& operator<<(std::ostream&, const myClass<U> &);
}

// Operator is a non-member and global, so it's not myClass<U>::operator<<()
// Because of how C++ implements templates the function must be
// fully declared in the header for the linker to resolve it :(
template <class U>
std::ostream& operator<<(std::ostream& os, const myClass<U> & obj)
{
  obj.toString(os);
  return os;
}

现在: * 如果我的toString()函数要放在cpp中,它就不能是内联的。 * 您将被困在头文件的一些代码中,我无法摆脱它。 * 该运算符将调用toString()方法,它不是内联的。
operator<<的主体可以在friend子句或类外声明。两个选项都很丑陋。:(
也许我误解或遗漏了什么,但是在gcc中只是前向声明运算符模板无法链接。
这也可以正常工作:
template class <T>
class myClass
{
    int myField;

    // Helper function accessing my fields
    void toString(std::ostream&) const;

    // For some reason this requires using T, and not U as above
    friend std::ostream& operator<<(std::ostream&, const myClass<T> &)
    {
        obj.toString(os);
        return os;
    }
}

如果您使用一个非模板的父类来实现operator<<并使用一个虚拟的toString()方法,您也可以避免在头文件中强制声明模板问题。


1

0
关键点是在operator<<之前加上logic::,并将其定义为友元函数。 logic::仅添加到成员函数之前。我理解这类似于告诉编译器此函数是成员函数,并授予相应权限(例如访问私有函数)。
换句话说,正如@asaelr和@Morteza所提到的,“在定义友元函数时,不使用类名来限定友元函数的名称”。
因此,我们应该在operator<<之前删除logic::

0
如果您将operator<<定义为成员函数,则其分解语法与使用非成员operator<<不同。非成员operator<<是二元运算符,而成员operator<<是一元运算符。
// Declarations
struct MyObj;
std::ostream& operator<<(std::ostream& os, const MyObj& myObj);

struct MyObj
{
    // This is a member unary-operator, hence one argument
    MyObj& operator<<(std::ostream& os) { os << *this; return *this; }

    int value = 8;
};

// This is a non-member binary-operator, 2 arguments
std::ostream& operator<<(std::ostream& os, const MyObj& myObj)
{
    return os << myObj.value;
}

那么……你真的怎样称呼它们呢?某些方面运算符很奇怪,我向你发起挑战,在你脑海中编写operator<<(...)语法以使事情变得合理。

MyObj mo;

// Calling the unary operator
mo << std::cout;

// which decomposes to...
mo.operator<<(std::cout);

或者你可以尝试调用非成员二元运算符:

MyObj mo;

// Calling the binary operator
std::cout << mo;

// which decomposes to...
operator<<(std::cout, mo);

当你将这些运算符变成成员函数时,你没有义务使它们的行为具有直观性,如果你想要的话,你可以定义operator<<(int)来左移一些成员变量,但要明白,无论你写了多少注释,人们可能会有点措手不及。

最后,可能会出现运算符调用的两个分解都有效的情况,这时你可能会遇到麻烦,我们将推迟讨论这个问题。

最后,请注意,编写一个看起来像二元运算符的一元成员运算符可能会很奇怪(因为你可以使成员运算符虚拟......也尝试不要陷入这条路......)

struct MyObj
{
    // Note that we now return the ostream
    std::ostream& operator<<(std::ostream& os) { os << *this; return os; }

    int value = 8;
};

这种语法现在会让很多程序员感到烦恼....

MyObj mo;

mo << std::cout << "Words words words";

// this decomposes to...
mo.operator<<(std::cout) << "Words words words";

// ... or even further ...
operator<<(mo.operator<<(std::cout), "Words words words");

注意这里的cout是链中的第二个参数...很奇怪,对吧?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接