运算符 << 应该作为友元函数还是成员函数来实现?
-
04-07-2019 - |
题
这基本上是一个问题,是否有一个“正确”的实施方式 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)
其中段落是一个字符串。
解决方案
这里的问题在于你对这篇文章的解释 关联.
这篇文章是关于有人在正确定义布尔关系运算符时遇到问题的。
运营商:
- 相等 == 和 !=
- 关系 < > <= >=
这些运算符应返回布尔值,因为它们正在比较相同类型的两个对象。将这些运算符定义为类的一部分通常是最简单的。这是因为类自动成为其自身的友元,因此 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;
}
其他提示
您不能将其作为成员函数执行,因为隐式 this
参数是&lt;&lt;
-operator的左侧。 (因此,您需要将其作为成员函数添加到 ostream
-class。不好:)
你可以在没有朋友
的情况下将其作为免费功能吗?这是我更喜欢的,因为它清楚地表明这是与 ostream
的集成,而不是你的类的核心功能。
如果可能,作为非成员和非成员函数。
正如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上)避免它。
希望这会有所帮助。
它应该被实现为一个免费的,非朋友的功能,特别是如果像现在大多数事情一样,输出主要用于诊断和记录。为需要进入输出的所有内容添加const访问器,然后让输出器调用它们并进行格式化。
我实际上已经开始在“ostreamhelpers”中收集所有这些ostream输出自由函数。标题和实现文件,它使次要功能远离类的真正目的。
签名:
bool operator<<(const obj&, const obj&);
似乎很可疑,这不符合 stream
约定,也不符合按位约定,因此它看起来像是运算符重载滥用的情况, operator&lt;
应返回 bool
但 operator&lt;&lt;&lt;
应该返回别的东西。
如果你的意思是这样说:
ostream& operator<<(ostream&, const obj&);
然后,因为你无法将函数添加到 ostream
,所以函数必须是一个自由函数,无论 friend
是否依赖于它必须访问的内容(如果它不需要访问私人或受保护的成员,则无需使其成为朋友)。
为了完成起见,我想补充一点,你确实可以创建一个运算符 ostream&amp;运算符&lt;&lt; (ostream&amp; 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;
}
总而言之 - 你可以做到,但你很可能不应该这样做:)
operator&lt;&lt;&lt;&lt;
作为朋友函数实现:
#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;
}
OUTPUT:100 Hello 100 Hello按任意键继续&#8230;
这可以是友元函数,因为该对象位于运算符&lt;&lt;
的右侧,而参数 cout
位于左侧。所以这不能成为类的成员函数,它只能是朋友函数。
朋友运营商=作为班级的平等权利
friend std::ostream& operator<<(std::ostream& os, const Object& object) {
os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
return os;
}