应该是运营商<<被实现为朋友还是成员函数?

时间:2008-10-25 18:19:26

标签: c++ operator-overloading

这基本上就是问题,是否有一种“正确”的方式来实施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 个答案:

答案 0 :(得分:106)

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

本文是关于正确定义bool关系运算符的问题的人。

运营商:

  • Equality ==和!=
  • 关系&lt; &GT; &lt; =&gt; =

这些运算符应该返回一个bool,因为它们正在比较两个相同类型的对象。通常最简单的方法是将这些运算符定义为类的一部分。这是因为一个类自动成为它自己的朋友,因此Paragraph类型的对象可以相互检查(甚至是彼此私有成员)。

有一个参数可以使这些自由站立功能,因为如果它们不是同一类型,则允许自动转换转换双方,而成员函数只允许自动转换rhs。我发现这是一个纸人争论,因为你并不真的希望首先发生自动转换(通常)。但如果这是你想要的东西(我不推荐它)那么让比较器自由站立可能是有利的。

流媒体运营商:

  • operator&lt;&lt;输出
  • 运算符&gt;&gt;输入

当您将这些用作流运算符(而不是二进制移位)时,第一个参数是流。由于您无权访问流对象(不是您自己修改的),因此这些不能是成员运算符,因此它们必须位于类的外部。因此,他们必须是班级的朋友,或者可以访问一个公共方法,为你做流式传输。

这些对象传统上也可以返回对流对象的引用,因此您可以将流操作链接在一起。

#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;
}

答案 1 :(得分:52)

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

你可以在没有friend的情况下将其作为免费功能吗?这是我更喜欢的,因为它清楚地表明这是与ostream的集成,而不是您班级的核心功能。

答案 2 :(得分:30)

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

正如Herb Sutter和Scott Meyers所描述的那样,非友好的非会员功能更喜欢成员函数,以帮助增加封装。

在某些情况下,就像C ++流一样,您将无法选择并且必须使用非成员函数。

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

关于运营商&lt;&lt;和&gt;&gt;原型

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

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

我甚至无法开始思考这种方法如何在流中发挥作用。

以下是实现&lt;&lt;&lt;&lt;&lt;&lt;&lt;和&gt;&gt;运算符。

假设你想要使用T类型的类似流的对象。

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

通用运算符&lt;&lt;和&gt;&gt;功能原型

第一个是作为功能:

// 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 ;
}

通用运算符&lt;&lt;和&gt;&gt;方法原型

第二种方法是:

// 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;和&gt;&gt; C ++流的运算符。

对于通用basic_istream和basic_ostream

注意是流的情况,因为你不能修改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 ;
}

Rhys Ulerich评论了这样一个事实,即基于字符的代码只是它上面的通用代码的“特化”。当然,Rhys是对的:我不建议使用基于char的例子。它只在这里给出,因为它更容易阅读。因为只有你只使用基于字符串的流才可行,所以你应该在wchar_t代码很常见的平台上(例如在Windows上)避免它。

希望这会有所帮助。

答案 3 :(得分:10)

它应该被实现为一个免费的,非朋友的功能,特别是如果像现在大多数事情一样,输出主要用于诊断和记录。为需要进入输出的所有内容添加const访问器,然后让输出器调用它们并进行格式化。

我实际上已经在“ostreamhelpers”标头和实现文件中收集了所有这些ostream输出自由函数,它使次要功能远离类的真正目的。

答案 4 :(得分:6)

签名:

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

似乎很可疑,这不符合stream约定,也不符合按位约定,因此看起来像是运算符重载滥用的情况,operator <应返回booloperator <<应该返回别的东西。

如果你的意思是这样说:

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

然后,由于你无法将函数添加到ostream,函数必须是一个自由函数,无论friend是否取决于它必须访问的内容(如果它不是需要访问私人或受保护的成员,没有必要让它成为朋友。)

答案 5 :(得分:2)

为了完成起见,我想补充一点,你确实可以在一个类中创建一个运算符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;
}

总而言之 - 你可以做到,但你很可能不应该这样做:)

答案 6 :(得分:0)

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你好按任意键继续......

这可以是友方函数,因为该对象位于operator<<的右侧,而参数cout位于左侧。所以这不能成为类的成员函数,它只能是朋友函数。

答案 7 :(得分:0)

朋友运营商=作为班级的平等权利

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