如何正确重载ostream <<操作符?


237

我正在用C ++写一个小的矩阵库,用于矩阵运算。但是我的编译器抱怨,以前没有。这段代码在架子上放置了6个月,在此之间,我将计算机从debian etch升级到lenny(g ++(Debian 4.3.2-1.1)4.3.2),但是在具有相同g ++的Ubuntu系统上,我遇到了同样的问题。

这是我的矩阵类的相关部分:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix);
    }
}

和“实现”:

using namespace Math;

std::ostream& Matrix::operator <<(std::ostream& stream, const Matrix& matrix) {

    [...]

}

这是编译器给出的错误:

matrix.cpp:459:错误:'std :: ostream&Math :: Matrix :: operator <<(std :: ostream&,const Math :: Matrix&)'必须正好采用一个参数

这个错误让我有些困惑,但是在过去的6个月里,我使用很多Java语言后,我的C ++再次变得有点生锈。:-)

Answers:


127

您已将函数声明为friend。它不是课程的成员。您应该Matrix::从实现中删除。friend表示指定的函数(不是类的成员)可以访问私有成员变量。实现函数的方式就像Matrix是错误的类的实例方法。


7
而且,您还应该在Math命名空间中声明它(不仅是使用正在使用的命名空间Math)。
大卫·罗德里格斯(DavidRodríguez)-德里贝斯2009年

1
为什么operator<<必须在的命名空间中Math?似乎它应该在全局名称空间中。我同意我的编译器希望它位于的命名空间中Math,但这对我来说没有意义。
Mark Lakata

抱歉,但是我看不到为什么在这里为什么要使用friend关键字?当在类中声明朋友运算符重写时,似乎无法使用Matrix :: operator <<(ostream&os,const Matrix&m)实现。取而代之的是,我们只需要使用全局运算符重写operator << ostream&os,const Matrix&m),那么为什么还要在类内首先声明它呢?
Patrick

139

只是告诉您另一种可能性:我喜欢为此使用朋友定义:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix) {
            [...]
        }
    };
}

该函数将自动定位到周围的名称空间中Math(即使其定义出现在该类的范围之内),但除非使用带有Matrix对象的operator <<,它将使依赖于参数的查找找到该运算符定义,否则该函数将不可见。有时这可能有助于模棱两可的调用,因为它对于除Matrix之外的参数类型是不可见的。在编写其定义时,您也可以直接引用Matrix中定义的名称以及Matrix本身,而无需使用一些可能长的前缀来限定名称并提供诸如的模板参数Math::Matrix<TypeA, N>


77

要添加到Mehrdad答案中,

namespace Math
{
    class Matrix
    {
       public:

       [...]


    }   
    std::ostream& operator<< (std::ostream& stream, const Math::Matrix& matrix);
}

在您的实施中

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }

4
我不明白为什么这是一次否定表决,这说明您可以声明运算符位于名称空间中,而不是作为朋友出现,以及如何声明运算符。
09年

2
Mehrdad的答案没有任何代码片段,因此我只是添加了将其移到名称空间本身的类之外的方法。
09年

我明白您的意思,我只看了您的第二段。但是现在我看到您将操作员带出了课堂。谢谢你的建议。
Matthias van der Vlies 09年

7
它不仅是出之类的,但它是正确定义里面的数学命名空间。此外,它还有一个额外的优势(可能不是针对Matrix,而是对于其他类),它可以是虚拟的,因此打印将在继承的最高级进行。
大卫·罗德里格斯(DavidRodríguez)-德里贝斯2009年

68

假设我们正在讨论operator <<从其派生std::ostream来处理Matrix该类的所有类的重载(而不是<<Matrix类重载),则在标头的Math命名空间之外声明重载函数更有意义。

仅当无法通过公共接口实现功能时,才使用朋友功能。

矩阵

namespace Math { 
    class Matrix { 
        //...
    };  
}
std::ostream& operator<<(std::ostream&, const Math::Matrix&);

请注意,运算符重载是在名称空间之外声明的。

矩阵文件

using namespace Math;
using namespace std;

ostream& operator<< (ostream& os, const Matrix& obj) {
    os << obj.getXYZ() << obj.getABC() << '\n';
    return os;
}

另一方面,如果确实需要让您的重载函数成为朋友,即需要访问私有成员和受保护成员。

数学

namespace Math {
    class Matrix {
        public:
            friend std::ostream& operator<<(std::ostream&, const Matrix&);
    };
}

您需要使用名称空间块(而不是)将函数定义括起来using namespace Math;

矩阵文件

using namespace Math;
using namespace std;

namespace Math {
    ostream& operator<<(ostream& os, const Matrix& obj) {
        os << obj.XYZ << obj.ABC << '\n';
        return os;
    }                 
}

38

在C ++ 14中,您可以使用以下模板来打印具有T :: print(std :: ostream&)const;的任何对象。会员。

template<class T>
auto operator<<(std::ostream& os, T const & t) -> decltype(t.print(os), os) 
{ 
    t.print(os); 
    return os; 
} 

在C ++ 20中,可以使用Concepts。

template<typename T>
concept Printable = requires(std::ostream& os, T const & t) {
    { t.print(os) };
};

template<Printable T>
std::ostream& operator<<(std::ostream& os, const T& t) { 
    t.print(os); 
    return os; 
} 

有趣的解决方案!一个问题-应该在哪里声明此运算符,例如在全局范围内?我认为它对所有可以用来模板化的类型都是可见的?
巴尼

@barney它可以和使用它的类一起位于您自己的命名空间中。
QuentinUK '16

std::ostream&因为它是返回类型,您难道不能只返回吗?
吉恩-迈克尔Celerier

5
@Jean-MichaëlCelerierdecltype确保仅在存在t :: print时使用此运算符。否则,它将尝试编译函数主体并给出编译错误。
QuentinUK

添加了概念版本,在此处进行了测试godbolt.org/z/u9fGbK
QuentinUK
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.