应该将operator <<实现为朋友还是成员函数?


129

这基本上就是问题,是否有“正确”的实施方法operator<<?阅读此内容,我可以看到类似以下内容的内容:

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) 

其中段落是一个字符串。


4
顺便说一句,您可能应该将const添加到成员函数的签名中
Motti,

4
为什么从operator <<返回bool?您是将其用作流运算符还是按位移位的重载?
马丁·约克

Answers:


120

问题在于您对所链接文章的解释。

平等

本文是关于某人在正确定义布尔关系运算符时遇到的问题。

运营商:

  • 等于==和!=
  • 关系<> <=> =

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

有一个论点可以使这些独立的函数起作用,因为如果它们不是同一类型,这将使​​自动转换可以转换双方,而成员函数仅允许将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;
}

19
为什么是operator<< private:
马特·克拉克森

47
@MattClarkson:不是。因此,它的一个朋友函数声明不属于该类,因此不受访问说明符的影响。我通常将朋友函数声明放在他们访问的数据旁边。
马丁·约克

12
如果您正在使用公共功能来访问数据,为什么它需要成为一个友好的功能?对不起,如果问题很愚蠢。
Semyon Danilov 2014年

4
@SemyonDanilov:为什么要破坏封装并添加吸气剂!freiend是一种扩展公共接口而不破坏封装的方法。阅读programs.stackexchange.com/a/99595/12917
Martin York

3
@LokiAstari但可以肯定的是,这是删除to_str或将其设为私有的参数。就目前而言,流运算符不必是朋友,因为它仅使用公共功能。
deworde

53

您不能将其作为成员函数使用,因为隐式this参数是<<-operator 的左侧。(因此,您需要将其作为成员函数添加到ostream-class中。不好:)

您可以不使用它而将其作为自由功能friend吗?那就是我喜欢的,因为它清楚表明这是与的集成ostream,而不是您课程的核心功能。


1
“这不是您班上的核心功能。” 那就是“朋友”的意思。如果它是核心功能,它将在类中,而不是朋友中。
xaxxon

1
@xaxxon我想我的第一句话解释了为什么在这种情况下将函数添加为成员函数是不可能的。一个friend函数作为一个成员函数相同的权利(是什么friend意思),所以作为类的用户,我会想知道为什么它会需要这一点。这就是我试图用“核心功能”一词来区分的区别。
Magnus Hoff

32

如果可能,作为非成员和非朋友功能。

如Herb Sutter和Scott Meyers所述,与成员函数相比,更喜欢非朋友非成员函数,以帮助增加封装性。

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

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

关于运算符<<和>>原型

我相信您在问题中举的例子是错误的。例如;

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

我什至无法开始考虑此方法如何在流中工作。

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

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

并且您要从中提取/插入/插入到Taragraph类型的对象的相关数据中。

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

首先是作为功能:

// 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 ++流,该怎么办?

这是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评论了基于char的代码只是其上方通用代码的“专业化”这一事实。当然,Rhys是对的:我不建议使用基于char的示例。仅在这里给出它是因为它更易于阅读。由于仅在使用基于字符的流时才可行,因此应在常见wchar_t代码的平台(例如Windows)上避免使用它。

希望这会有所帮助。


您的通用basic_istream和basic_ostream模板代码是否已经涵盖了std :: ostream和std :: istream特定的版本,因为后两个仅仅是前者使用char的实例化?
Rhys Ulerich 09年

@Rhys Ulerich:当然。我仅使用通用的模板版本,如果仅是因为在Windows上,您必须同时处理char和wchar_t代码。第二个版本的唯一优点是看起来比第一个版本更简单。我会澄清我的帖子。
paercebal,2009年

10

应该将其实现为一种免费的非朋友功能,尤其是像当今大多数情况一样,输出主要用于诊断和日志记录时。为需要输入输出的所有内容添加const访问器,然后让输出器仅调用它们并进行格式化。

实际上,我已经将所有这些ostream输出免费功能收集在“ ostreamhelpers”标头和实现文件中,它使辅助功能远离类的实际用途。


7

签名:

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

似乎stream有点怀疑,这既不符合约定也不符合按位约定,因此看起来像是运算符重载滥用的情况,operator <应该返回booloperator <<应该返回其他内容。

如果您这样说:

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

然后,由于您不能ostream根据需要向其中添加功能,因此该功能必须是自由功能,无论它是否friend要访问它必须访问的内容(如果它不需要访问私有成员或受保护的成员,那么都不需要使其成为自由函数。朋友)。


值得一提的ostream是,使用ostream.operator<<(obj&)订购时需要修改权限;因此是自由功能。否则,用户类型必须是蒸汽类型以适应访问。
wulfgarpro

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

综上所述-您可以做到,但您很可能不应该:)


0

朋友运算符=与类同等的权利

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

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 Hello
100 Hello

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


我不认为有一种方法可以将此作为成员功能来编写!
Rohit Vipin Mathews'2

为什么一切都大胆。让我删除它。
塞巴斯蒂安·马赫2014年
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.