【问题标题】:How to properly overload the << operator for an ostream?如何正确重载 ostream 的 << 运算符?
【发布时间】:2010-10-03 08:09:55
【问题描述】:

我正在用 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& 数学::矩阵::运算符

我对这个错误有点困惑,但是在这 6 个月里做了很多 Java 之后,我的 C++ 又变得有点生疏了。 :-)

【问题讨论】:

    标签: c++ namespaces operator-overloading iostream ostream


    【解决方案1】:

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

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

    该函数将自动定位到周围的命名空间Math(即使它的定义出现在该类的范围内),但除非您使用 Matrix 对象调用 operatorMath::Matrix<TypeA, N>。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 你还应该在 Math 命名空间中声明它(不仅仅是使用命名空间 Math)。
      • 为什么operator&lt;&lt; 必须在Math 的命名空间中?似乎它应该在全局命名空间中。我同意我的编译器希望它位于 Math 的命名空间中,但这对我来说没有意义。
      • 对不起,我看不懂为什么我们在这里使用friend关键字?当在类中声明友元运算符覆盖时,似乎我们无法使用 Matrix::operator
      【解决方案3】:

      要添加到 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;
       }
      

      【讨论】:

      • Mehrdad 的答案没有任何 sn-p 代码,所以我只是添加了可能通过将其移到命名空间本身的类之外的方法。
      • 我明白你的意思,我只看了你的第二个sn-p。但现在我看到你把接线员带出了课堂。感谢您的建议。
      • 它不仅在类之外,而且在数学命名空间内部中正确定义。它还具有额外的优势(可能不是矩阵,而是其他类)“打印”可以是虚拟的,因此打印将发生在最派生的继承级别。
      【解决方案4】:

      假设我们正在讨论为从std::ostream 派生的所有类重载operator &lt;&lt; 以处理Matrix 类(而不是为Matrix 类重载&lt;&lt;),那么声明头文件中 Math 命名空间之外的重载函数。

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

      Matrix.h

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

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

      Matrix.cpp

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

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

      数学.h

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

      您需要用命名空间块而不是 using namespace Math; 来封装函数定义。

      Matrix.cpp

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

      【讨论】:

      • 这里有点吹毛求疵.. 在这种情况下,我发现 os 是一个糟糕的缩写(它与“操作系统”绑定太多)
      【解决方案5】:

      在 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 中可以使用概念。

      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 它可能与使用它的类一起在您自己的命名空间中。
      • 你不能只返回std::ostream&amp;,因为它是返回类型吗?
      • @Jean-MichaëlCelerier decltype 确保此运算符仅在 t::print 存在时使用。否则它将尝试编译函数体并给出编译错误。
      • 添加了概念版本,在这里测试godbolt.org/z/u9fGbK
      【解决方案6】:

      我想通过一个重载&lt;&lt; 以打印数组的示例来简化这一点。

      1. 首先在&lt;&lt; 运算符周围传递这两种对象类型
      2. 创建一个函数来重载运算符,如下所示。
      #include<iostream> 
      using namespace std;
      
      void operator<<(ostream& os, int arr[]) {
          for (int i = 0;i < 10;i++) {
              cout << arr[i] << " ";
          }
          cout << endl; 
      }
          
      int main() {
          int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
          cout << arr;
      }
      

      如果还需要级联运算符,请确保返回 cout 对象 在重载函数中如下,

      #include<iostream> 
      using namespace std;
      
      ostream& operator<<(ostream& os, int arr[]) {
          for (int i = 0;i < 10;i++) {
              cout << arr[i] << " ";
          }
          cout << endl; 
          return os;
      }
          
      int main() {
          int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
          int arr2[10] = { 11,22,33,44,55,66,77,88,99,100 };
          // cascading of operators
          cout << arr << arr2;
      }
      

      【讨论】:

      • 你可以让它与任何大小的数组一起工作:- template&lt;int N&gt; ostream&amp; operator&lt;&lt;(ostream&amp; os, int(&amp; arr)[N]) { etc
      猜你喜欢
      • 1970-01-01
      • 2013-10-10
      • 2011-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多