代码之家  ›  专栏  ›  技术社区  ›  Steve

为什么这个<<重载编译

  •  2
  • Steve  · 技术社区  · 15 年前

    我不明白为什么下面的代码不编译。语法与我的其他运算符重载相同。是否有限制<<过载必须友好?如果是,为什么?谢谢你的帮助。

    这不管用-

    #include "stdafx.h"
    #include <iostream>
    #include <fstream>
    #include <string>
    
    class Test
    {
     public:
    explicit Test(int var):
        m_Var(var)
        {   }
    
        std::ostream& operator<< (std::ostream& stream)
        {
            return stream << m_Var;
        }
     private:
    int m_Var;
    
     };
    
     int _tmain(int argc, _TCHAR* argv[])
     {
    Test temp(5);
    
    std::cout << temp;
    
    return 0;
    }
    

    这确实有效-

    #include "stdafx.h"
    #include <iostream>
    #include <fstream>
    #include <string>
    
    class Test
    {
    public:
    explicit Test(int var):
        m_Var(var)
        {   }
    
        friend std::ostream& operator<< (std::ostream& stream, Test& temp);
    
    private:
        int m_Var;
    
     };
    
     std::ostream& operator<< (std::ostream& stream, Test& temp)
     {
    return stream << temp.m_Var;
     };
    
     int _tmain(int argc, _TCHAR* argv[])
     {
    Test temp(5);
    
    std::cout << temp;
    
    return 0;
     }
    
    6 回复  |  直到 15 年前
        1
  •  4
  •   John Dibling    15 年前

    这就是为什么流操作符必须是朋友的根本原因。

    获取此代码:

       struct Gizmo
        {
            ostream& operator<<(ostream& os) const
            {
                os << 42;
            }
        };
    
    
        int main()
        {
            Gizmo g;
            cout << g;
            return 0;
        }
    

    考虑调用的上下文 cout << g; 编译器编译此函数时,首先尝试执行以下操作:

    cout.operator<<(g);
    

    …如果找不到,则在全局命名空间中查找:

    operator<<(cout, g);
    

    …如果找不到,就无法编译。

    但是,当您尝试将流插入运算符作为gizmo的成员实现时,您希望编译器将您的代码解析为:

    g.operator<<(cout);
    

    …除非您将代码更改为:

    g << cout;
    

    ……这显然不是你想要的。

        2
  •  9
  •   Mehrdad Afshari    15 年前

    这不是任何运算符必须 friend ED“。问题是,如果将运算符声明为实例方法,则第一个参数总是强制为类本身的类型。在这种情况下,需要运算符的第一个参数(左侧)是 std::ostream& 因此,不能使用实例方法重载它,必须使用全局函数。

    顺便说一句,完全不要求声明为单独函数的运算符声明为 朋友 功能也一样。他们可以快乐地工作而不是成为 朋友 只要他们能进入 public 他们的论点。

        3
  •  6
  •   kennytm    15 年前

    因为第一个表单重载 temp << std::cout .

        4
  •  3
  •   Yogesh Arora    15 年前

    当你在做的时候 std::cout << temp; 这意味着你正在申请 << 接线员到 std::cout (因为运算符是左关联的)。如果要编写一个作为成员函数的运算符来实现此目的,则必须重载 << 操作员到任何类别 STD:: 属于,这是不可能的,因为这是你不能修改的。

    所以你必须编写一个函数来启用它,一种方法是重载 << 在带有两个参数的全局命名空间中,流以及要显示到控制台的对象。像这样的东西

    std::ostream& operator<< (std::ostream& stream, Test& temp)

    现在你要么交朋友要么不交。如果你不让它成为朋友,你就必须提供getter函数(比如 getMVar )为您提供会员的价值 Test 上课。不过,这不是一个好办法。因为这不需要提供getter函数。所以一般来说,交这样的运营商朋友是一种惯例。

    如前所述,您所做的将导致代码被编写为 temp << std::cout 这显然不是你想要的。

        5
  •  3
  •   Roger Pate    15 年前

    当作为成员函数实现时,运算符重载有一个隐式的第一个参数 . 对于溪流来说,这是不正常的:溪流必须先来。

    使用friend运算符简短,可以防止意外的隐式转换(由于仅通过adl使用)。如果您想定义它(例如在一个implementation.cpp文件中),那么让它调用一个非公共的并且可能是虚拟的方法:

    struct T {
      template<class Ch, class Tr>
      friend
      std::basic_ostream<Ch, Tr>& operator<<(
        std::basic_ostream<Ch, Tr>& s,
        T const& v
      ) {
        s << v.stuff; // or call v.output(s), etc.
        return s;
      }
    
    private:
      int stuff = 0; // initialization here is c++0x only
    
      //virtual void output(std::ostream&) const;
      //virtual void output(std::wostream&) const;
      // etc., or make it a template, but if it's short,
      // just put it in the above friend overload
    };
    

    加分: 将不具有的运算符重载成员命名为 . (提示:它们是静态的。)

        6
  •  0
  •   Yukiko    15 年前

    Scott Meyers在《有效C++第二版》中的总结 项目19:区分成员函数、非成员函数和友元函数 :

    operator和operator从来不是成员。如果f是operator>gt;或operator<<,则使f成为非成员函数。此外,如果F需要接触C的非公开成员,请让F成为C的朋友

    此声明只是决定是否成为operator<<和operator>>成员的一个准则。最好让他们成为非会员。如果你愿意,你可以让他们成为会员,但如果你愿意,你将被迫写:

    temp << std::cout // when calling operator<<
    temp >> std::cin  // when calling operator>>
    

    实际上,可以通过更改对STD::CUT到上面的表单的调用来纠正第一段代码。但这种写作方式绝对不自然。

    关于操作人员(作为非成员)的友好关系,如果任何一个操作人员需要访问私有/受保护的数据成员(如您的情况),那么它必须是类外部的朋友。