运算符 < < 应该作为朋友函数还是作为成员函数实现?

这基本上就是一个问题,有没有一种“正确”的方式来实现 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)

段落是一个字符串。

160279 次浏览

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

你能做到这一点作为一个免费的功能,而不是 friend0吗?这正是我所喜欢的,因为它清楚地表明这是与 ostream的集成,而不是类的核心功能。

签名:

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

似乎有点可疑,这不符合 stream惯例,也不符合按位惯例,所以它看起来像一个滥用运算符重载的案例,operator <应该返回 bool,但是 operator <<可能应该返回其他东西。

如果你是这个意思,就说:

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

然后,由于你不能添加函数到 ostream的必要性,该函数必须是一个自由函数,它是否是一个 friend取决于它有什么访问(如果它不需要访问私有或受保护的成员,没有必要使它的朋友)。

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

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

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

平等

本文是关于某人在正确定义 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;
}

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

正如 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 + + 流呢?

下面是 C + + 流相同的 < < 和 > > 运算符的原型。

对于通用的 basic _ iststream 和 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 是对的: 我不推荐使用基于字符的示例。这里给出的唯一原因是因为它更容易阅读。因为只有在使用基于字符的流时才可行,所以应该避免在 wchar _ t 代码常见的平台上使用它(例如在 Windows 上)。

希望这个能帮上忙。

作为朋友功能实现的 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;


}

产出:
你好
你好

这只能是一个朋友函数,因为对象在 operator<<的右边,参数 cout在左边。所以这不能是类的成员函数它只能是朋友函数。

朋友操作符 = 与类同等的权利

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

只是为了完成的目的,我想补充的是,您确实 可以创建一个操作符 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;
}

总而言之,你可以这么做,但是你最好不要这么做:)