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

发布于 2024-07-07 12:39:10 字数 784 浏览 7 评论 0 原文

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

其中段落是一个字符串。

That's basically the question, is there a "right" way to implement operator<< ?
Reading this I can see that something like:

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

is preferred to something like

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

But I can't quite see why should I use one or the other.

My personal case is:

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

But I could probably do:

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

What rationale should I base this decision on?

Note:

 Paragraph::to_str = (return paragraph) 

where paragraph's a string.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

巡山小妖精 2024-07-14 12:39:10

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

平等

这篇文章是关于有人在正确定义布尔关系运算符时遇到问题的。

运算符:

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

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

对于创建这些独立函数有一个争论,因为这可以让自动转换在双方类型不同时进行转换,而成员函数只允许自动转换 rhs。 我发现这是一个纸上谈兵的论点,因为你真的不希望自动转换首先发生(通常)。 但如果这是您想要的(我不推荐),那么使比较器独立式可能会更有利。

Streaming

流操作符:

  • 操作符<< 输出
  • 运算符>> 输入

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

这些对象通常返回对流对象的引用,以便您可以将流操作链接在一起。

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

The problem here is in your interpretation of the article you link.

Equality

This article is about somebody that is having problems correctly defining the bool relationship operators.

The operator:

  • Equality == and !=
  • Relationship < > <= >=

These operators should return a bool as they are comparing two objects of the same type. It is usually easiest to define these operators as part of the class. This is because a class is automatically a friend of itself so objects of type Paragraph can examine each other (even each others private members).

There is an argument for making these free standing functions as this lets auto conversion convert both sides if they are not the same type, while member functions only allow the rhs to be auto converted. I find this a paper man argument as you don't really want auto conversion happening in the first place (usually). But if this is something you want (I don't recommend it) then making the comparators free standing can be advantageous.

Streaming

The stream operators:

  • operator << output
  • operator >> input

When you use these as stream operators (rather than binary shift) the first parameter is a stream. Since you do not have access to the stream object (its not yours to modify) these can not be member operators they have to be external to the class. Thus they must either be friends of the class or have access to a public method that will do the streaming for you.

It is also traditional for these objects to return a reference to a stream object so you can chain stream operations together.

#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;
}
心的憧憬 2024-07-14 12:39:10

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

您能否将其作为一个自由函数而不对其进行 friend 处理? 这就是我更喜欢的,因为它清楚地表明这是与 ostream 的集成,而不是您的类的核心功能。

You can not do it as a member function, because the implicit this parameter is the left hand side of the <<-operator. (Hence, you would need to add it as a member function to the ostream-class. Not good :)

Could you do it as a free function without friending it? That's what I prefer, because it makes it clear that this is an integration with ostream, and not a core functionality of your class.

め七分饶幸 2024-07-14 12:39:10

如果可能的话,作为非成员和非好友功能。

正如 Herb Sutter 和 Scott Meyers 所描述的,与成员函数相比,更喜欢非友元非成员函数,以帮助提高封装性。

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

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

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

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

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

我什至无法开始思考这种方法如何在流中工作。

以下是实现<<的两种方法: 和>> 运营商。

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

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

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

第一个是函数:

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

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

// 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 上)上避免使用它。

希望这会有所帮助。

If possible, as non-member and non-friend functions.

As described by Herb Sutter and Scott Meyers, prefer non-friend non-member functions to member functions, to help increase encapsulation.

In some cases, like C++ streams, you won't have the choice and must use non-member functions.

But still, it does not mean you have to make these functions friends of your classes: These functions can still acess your class through your class accessors. If you succeed in writting those functions this way, then you won.

About operator << and >> prototypes

I believe the examples you gave in your question are wrong. For example;

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

I can't even start to think how this method could work in a stream.

Here are the two ways to implement the << and >> operators.

Let's say you want to use a stream-like object of type T.

And that you want to extract/insert from/into T the relevant data of your object of type Paragraph.

Generic operator << and >> function prototypes

The first being as functions:

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

Generic operator << and >> method prototypes

The second being as methods:

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

Note that to use this notation, you must extend T's class declaration. For STL objects, this is not possible (you are not supposed to modify them...).

And what if T is a C++ stream?

Here are the prototypes of the same << and >> operators for C++ streams.

For generic basic_istream and basic_ostream

Note that is case of streams, as you can't modify the C++ stream, you must implement the functions. Which means something like:

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

For char istream and ostream

The following code will work only for char-based streams.

// 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 commented about the fact the char-based code is but a "specialization" of the generic code above it. Of course, Rhys is right: I don't recommend the use of the char-based example. It is only given here because it's simpler to read. As it is only viable if you only work with char-based streams, you should avoid it on platforms where wchar_t code is common (i.e. on Windows).

Hope this will help.

孤檠 2024-07-14 12:39:10

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

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

It should be implemented as a free, non-friend functions, especially if, like most things these days, the output is mainly used for diagnostics and logging. Add const accessors for all the things that need to go into the output, and then have the outputter just call those and do formatting.

I've actually taken to collecting all of these ostream output free functions in an "ostreamhelpers" header and implementation file, it keeps that secondary functionality far away from the real purpose of the classes.

剩一世无双 2024-07-14 12:39:10

签名:

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

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

如果你的意思是这样说:

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

那么既然你不能向 ostream 添加函数,那么该函数必须是一个自由函数,它是否是 friend 取决于它是什么必须访问(如果不需要访问私有或受保护的成员,则无需将其设为好友)。

The signature:

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

Seems rather suspect, this does not fit the stream convention nor the bitwise convention so it looks like a case of operator overloading abuse, operator < should return bool but operator << should probably return something else.

If you meant so say:

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

Then since you can't add functions to ostream by necessity the function must be a free function, whether it a friend or not depends on what it has to access (if it doesn't need to access private or protected members there's no need to make it friend).

赢得她心 2024-07-14 12:39:10

为了完整起见,我想补充一点,您确实可以创建一个运算符ostream& 运算符 << (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;
}

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

Just for completion sake, I would like to add that you indeed can create an operator ostream& operator << (ostream& os) inside a class and it can work. From what I know it's not a good idea to use it, because it's very convoluted and unintuitive.

Let's assume we have this code:

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

So to sum it up - you can do it, but you most probably shouldn't :)

唐婉 2024-07-14 12:39:10

我建议将 operator<< 定义为私人好友,又名 隐藏好友,以便只能通过依赖于参数的查找来找到它

class Paragraph {
 // ...
 private:
  friend std::ostream& operator<<(std::ostream& os, const Paragraph& p) {
    return os << p.to_str();
  }
};

隐藏的朋友有很多好处,最重要的是它不会污染重载集,可以加快编译速度并改进诊断。 它还可以防止不需要的隐式转换。

I would recommend defining operator<< as a private friend aka hidden friend so that it is only found via the argument-dependent lookup:

class Paragraph {
 // ...
 private:
  friend std::ostream& operator<<(std::ostream& os, const Paragraph& p) {
    return os << p.to_str();
  }
};

Hidden friends have a number of benefits, most importantly it won't pollute the overload set which can speed up compilation and improve diagnostics. It also prevents undesirable implicit conversions.

浪漫人生路 2024-07-14 12:39:10

友元运算符 = 与类平等的权利

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

friend operator = equal rights as class

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}
短叹 2024-07-14 12:39: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 位于左侧。 所以这不能是类的成员函数,它只能是友元函数。

operator<< implemented as a friend function:

#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

This can be a friend function only because the object is on the right hand side of operator<< and argument cout is on the left hand side. So this can't be a member function to the class, it can only be a friend function.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文