代码之家  ›  专栏  ›  技术社区  ›  Federico Builes

运算符<<应该作为友元或成员函数实现吗?

  •  114
  • Federico Builes  · 技术社区  · 16 年前

    这基本上是个问题,有没有一个“正确”的方法来实现 operator<< ? 阅读 this 我可以看到这样的情况:

    friend bool operator<<(obj const& lhs, obj const& rhs);
    

    比类似的东西好

    ostream& operator<<(obj const& rhs);
    

    但我不太明白我为什么要用一个或另一个。

    我个人的情况是:

    friend ostream & operator<<(ostream &os, const Paragraph& p) {
        return os << p.to_str();
    }
    

    但我可能会这样做:

    ostream & operator<<(ostream &os) {
        return os << paragraph;
    }
    

    我应该根据什么理由做出这个决定?

    注释 :

     Paragraph::to_str = (return paragraph) 
    

    其中段落是字符串。

    8 回复  |  直到 7 年前
        1
  •  103
  •   Loki Astari    11 年前

    这里的问题在于你对你的文章的解释 link .

    这篇文章是关于一个在正确定义bool关系操作符时遇到问题的人。

    操作员:

    • 相等==和!=
    • 关系<><=>。=

    这些运算符在比较同一类型的两个对象时应返回bool。通常最容易将这些运算符定义为类的一部分。这是因为类自动成为自己的朋友,所以段落类型的对象可以互相检查(甚至是彼此的私有成员)。

    有一个参数可以使这些独立的函数成为独立的函数,因为它允许自动转换两侧(如果它们不是同一类型),而成员函数只允许自动转换RHS。我觉得这是一个纸上谈兵的论点,因为你不希望自动转换在第一时间发生(通常)。但是如果这是你想要的(我不建议这样做),那么让比较器独立是有利的。

    流运算符:

    • 操作员<<输出
    • 操作员输入

    当您使用这些作为流运算符(而不是二进制移位)时,第一个参数是流。因为您没有访问流对象的权限(不能修改流对象),所以这些不能是成员运算符,它们必须是类外部的。因此,它们要么是类的朋友,要么可以访问为您进行流式处理的公共方法。

    这些对象返回对流对象的引用也是传统的做法,这样您就可以将流操作链接在一起。

    #include <iostream>
    
    class Paragraph
    {
        public:
            explicit Paragraph(std::string const& init)
                :m_para(init)
            {}
    
            std::string const&  to_str() const
            {
                return m_para;
            }
    
            bool operator==(Paragraph const& rhs) const
            {
                return m_para == rhs.m_para;
            }
            bool operator!=(Paragraph const& rhs) const
            {
                // Define != operator in terms of the == operator
                return !(this->operator==(rhs));
            }
            bool operator<(Paragraph const& rhs) const
            {
                return  m_para < rhs.m_para;
            }
        private:
            friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
            std::string     m_para;
    };
    
    std::ostream & operator<<(std::ostream &os, const Paragraph& p)
    {
        return os << p.to_str();
    }
    
    
    int main()
    {
        Paragraph   p("Plop");
        Paragraph   q(p);
    
        std::cout << p << std::endl << (p == q) << std::endl;
    }
    
        2
  •  51
  •   Magnus Hoff    16 年前

    不能将其作为成员函数执行,因为 this 参数是 << -运算符。(因此,您需要将其作为成员函数添加到 ostream 一流的。不好:

    你能把它作为一个自由的功能来做吗? friend 它呢?这就是我喜欢的,因为它清楚地表明这是一个与 溪流 而不是类的核心功能。

        3
  •  30
  •   paercebal    15 年前

    如果可能,作为非成员和非友元函数。

    正如Herb Sutter和Scott Meyers所描述的,比起成员函数,更喜欢非友元非成员函数,以帮助增加封装。

    在某些情况下,像C++流一样,您将没有选择,必须使用非成员函数。

    但是,这并不意味着您必须使这些函数成为类的朋友:这些函数仍然可以通过类访问器访问类。如果你成功地用这种方式编写了这些函数,那么你就赢了。

    关于操作员<<和>>原型

    我相信你在问题中给出的例子是错误的。例如;

    ostream & operator<<(ostream &os) {
        return os << paragraph;
    }
    

    我甚至不能开始思考这个方法如何在流中工作。

    以下是实现<<和>>运算符的两种方法。

    假设您想要使用一个类型为t的类似流的对象。

    并且您希望从t中提取/插入段落类型的对象的相关数据。

    通用运算符<<和>>函数原型

    第一个功能是:

    // T << Paragraph
    T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph)
    {
       // do the insertion of p_oParagraph
       return p_oOutputStream ;
    }
    
    // T >> Paragraph
    T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph)
    {
       // do the extraction of p_oParagraph
       return p_oInputStream ;
    }
    

    通用运算符<<和>>方法原型

    第二种方法是:

    // T << Paragraph
    T & T::operator << (const Paragraph & p_oParagraph)
    {
       // do the insertion of p_oParagraph
       return *this ;
    }
    
    // T >> Paragraph
    T & T::operator >> (const Paragraph & p_oParagraph)
    {
       // do the extraction of p_oParagraph
       return *this ;
    }
    

    注意,要使用这个符号,必须扩展T的类声明。对于STL对象,这是不可能的(您不应该修改它们…)。

    如果T是C++流怎么办?

    下面是相同的& lt;& lt;and & gt;& gt;C++流的运算符的原型。

    对于一般的基本组和基本组

    注意,是流的情况,因为不能修改C++流,必须实现函数。这意味着:

    // OUTPUT << Paragraph
    template <typename charT, typename traits>
    std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
    {
       // do the insertion of p_oParagraph
       return p_oOutputStream ;
    }
    
    // INPUT >> Paragraph
    template <typename charT, typename traits>
    std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & p_oInputStream, const CMyObject & p_oParagraph)
    {
       // do the extract of p_oParagraph
       return p_oInputStream ;
    }
    

    用于char istream和ostream

    以下代码仅适用于基于字符的流。

    // OUTPUT << A
    std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph)
    {
       // do the insertion of p_oParagraph
       return p_oOutputStream ;
    }
    
    // INPUT >> A
    std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph)
    {
       // do the extract of p_oParagraph
       return p_oInputStream ;
    }
    

    RhysUlerich评论了基于字符的代码只是上面的通用代码的“专门化”。当然,Rhys是对的:我不建议使用基于char的示例。这是因为它更容易阅读。因为只有当您只使用基于字符的流时,它才是可行的,所以您应该避免在wchar_t代码很常见的平台上使用它(即在Windows上)。

    希望这会有所帮助。

        4
  •  10
  •   XPav    16 年前

    它应该作为一个自由的、非友元的函数来实现,特别是像现在的大多数事情一样,输出主要用于诊断和日志记录。为需要进入输出的所有内容添加const访问器,然后让输出器调用这些内容并进行格式化。

    实际上,我已经开始收集“ostreamhelpers”头文件和实现文件中的所有这些ostream输出自由函数,它使辅助功能远离类的真正用途。

        5
  •  6
  •   Motti    16 年前

    签名:

    bool operator<<(const obj&, const obj&);
    

    似乎很可疑,这不适合 stream 约定也不是按位约定,所以看起来像是运算符重载滥用, operator < 应该返回 bool 但是 operator << 应该还点别的东西。

    如果你想这么说,就说:

    ostream& operator<<(ostream&, const obj&); 
    

    既然你不能在 ostream 必要时,函数必须是自由函数,无论它是 friend 或者不取决于它必须访问什么(如果它不需要访问私有或受保护的成员,就不需要使它成为朋友)。

        6
  •  2
  •   ashrasmun    7 年前

    为了完成任务,我想补充一句 可以 创建操作员 ostream& operator << (ostream& os) 在一个类中,它可以工作。据我所知,使用它不是一个好主意,因为它是非常复杂和无意义的。

    假设我们有这个代码:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    struct Widget
    {
        string name;
    
        Widget(string _name) : name(_name) {}
    
        ostream& operator << (ostream& os)
        {
            return os << name;
        }
    };
    
    int main()
    {
        Widget w1("w1");
        Widget w2("w2");
    
        // These two won't work
        {
            // Error: operand types are std::ostream << std::ostream
            // cout << w1.operator<<(cout) << '\n';
    
            // Error: operand types are std::ostream << Widget
            // cout << w1 << '\n';
        }
    
        // However these two work
        {
            w1 << cout << '\n';
    
            // Call to w1.operator<<(cout) returns a reference to ostream&
            w2 << w1.operator<<(cout) << '\n';
        }
    
        return 0;
    }
    

    所以总结一下-你可以做到,但你很可能不应该这样做:)

        7
  •  0
  •   Sebastian Mach    10 年前

    operator<< 作为朋友功能实现:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Samp
    {
    public:
        int ID;
        string strName; 
        friend std::ostream& operator<<(std::ostream &os, const Samp& obj);
    };
     std::ostream& operator<<(std::ostream &os, const Samp& obj)
        {
            os << obj.ID<< “ ” << obj.strName;
            return os;
        }
    
    int main()
    {
       Samp obj, obj1;
        obj.ID = 100;
        obj.strName = "Hello";
        obj1=obj;
        cout << obj <<endl<< obj1;
    
    } 
    

    输出:100你好100你好按任意键继续

    这可以是友元函数,因为对象位于 操作员<< 论证 cout 在左手边。所以这不能是类的成员函数,它只能是友元函数。

        8
  •  0
  •   Nehigienix    8 年前

    friend operator=与类相同的权限

    friend std::ostream& operator<<(std::ostream& os, const Object& object) {
        os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
        return os;
    }