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を返す理由 ストリーム演算子として、またはビット単位のシフトのオーバーロードとして使用していますか?
マーティンヨーク

回答:


120

ここでの問題は、リンクした記事の解釈にあります。

平等

この記事は、ブール関係演算子を正しく定義するのに問題がある誰かについてです。

オペレーター:

  • 平等==および!=
  • 関係<> <=> =

これらの演算子は、同じタイプの2つのオブジェクトを比較しているため、ブール値を返す必要があります。通常、これらの演算子をクラスの一部として定義するのが最も簡単です。これは、クラスが自動的にそれ自体のフレンドになるため、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:ですか?
マットクラークソン2013年

47
@MattClarkson:そうではありません。したがって、そのフレンド関数宣言はクラスの一部ではないため、アクセス指定子の影響を受けません。私は通常、フレンドがアクセスするデータの隣にフレンド関数宣言を置きます。
マーティンヨーク

12
データにアクセスするためにパブリック関数を使用している場合、なぜそれはフレンドリーな関数である必要があるのですか?質問が愚かである場合は申し訳ありません。
Semyon Danilov 14年

4
@SemyonDanilov:なぜカプセル化を解除してゲッターを追加するのですか?freiendカプセル化を解除せずにパブリックインターフェイスを拡張する方法です。programmers.stackexchange.com/a/99595/12917
マーティンヨーク

3
@LokiAstariしかし、確かにそれはto_strを削除するか、それを非公開にするための議論です。現状では、ストリーミングオペレーターはパブリック関数のみを使用しているため、フレンドである必要はありません。
deworde 2017年

53

暗黙的なthisパラメーターは<<-演算子の左側であるため、メンバー関数としては実行できません。(したがって、それをメンバー関数として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;
}

このメソッドがストリームでどのように機能するかを考えることすらできません。

<<演算子と>>演算子を実装する2つの方法を次に示します。

タイプ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 ;
}

ジェネリック演算子の<<および>>メソッドのプロトタイプ

2番目はメソッドとして存在します。

// 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さんは、charベースのコードがその上の一般的なコードの「特殊化」に過ぎないという事実についてコメントしました。もちろん、Rhysは正しいです。charベースの例の使用はお勧めしません。読みやすいので、ここでのみ示します。charベースのストリームのみを処理する場合にのみ実行可能であるため、wchar_tコードが一般的なプラットフォーム(Windowsなど)では回避する必要があります。

これがお役に立てば幸いです。


後者の2つは文字を使用した前者の単なるインスタンス化であるため、一般的なbasic_istreamおよびbasic_ostreamテンプレートコードはすでにstd :: ostream-およびstd :: istream-specificバージョンをカバーしていませんか?
Rhys Ulerich、2009

@Rhys Ulerich:もちろんです。Windowsでは、charとwchar_tの両方のコードを処理する必要があるため、私は汎用のテンプレートバージョンのみを使用しています。2番目のバージョンの唯一のメリットは、最初のバージョンよりもシンプルに見えることです。それについて私の投稿を明確にします。
paercebal 2009

10

特に最近のほとんどのことのように、出力が主に診断とロギングに使用される場合は特に、無料の非フレンド機能として実装する必要があります。出力に移動する必要があるすべてのものにconstアクセサーを追加し、出力側にそれらを呼び出してフォーマットを実行させる。

私は実際に、これらのすべてのostream出力フリー関数を「ostreamhelpers」ヘッダーおよび実装ファイルに収集することにしました。それにより、その二次機能がクラスの実際の目的から遠く離れています。


7

署名:

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

むしろ疑わしいと思われる、これが合わないstream、それは虐待を演算子オーバーロードの場合のように見えるので、大会でもビット単位の規則を、operator <返す必要がありboolますがoperator <<、おそらく何かを返す必要があります。

もしそうならあなたは言う:

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

次に、ostream必要に応じて関数を追加できないため、関数はフリー関数であるfriend必要があります。アクセスする必要があるかどうかに依存します(プライベートまたは保護されたメンバーにアクセスする必要がない場合は、関数を作成する必要はありません)友人)。


順序付けostreamを使用する場合は、変更するためのアクセス権が必要になることに言及する価値がありostream.operator<<(obj&)ます。したがって、無料の機能です。それ以外の場合、ユーザータイプはアクセスに対応するためにスチームタイプである必要があります。
wulfgarpro 2016年

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ハロー
100ハロー

オブジェクトが右側にありoperator<<、引数coutが左側にあるため、これはフレンド関数になることができます。したがって、これはクラスのメンバー関数にすることはできず、フレンド関数のみにすることができます。


これをメンバー関数として書く方法はないと思います!!
Rohit Vipin Mathews

なぜすべてが大胆なのですか。これを削除させてください。
セバスチャンマッハ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.