web-dev-qa-db-ja.com

operator <<を友人またはメンバー関数として実装する必要がありますか?

それは基本的に質問です、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) 

ここで、段落は文字列です。

117
Federico Builes

ここでの問題は、記事 link の解釈にあります。

この記事は、bool関係演算子を正しく定義するのに問題がある人に関するものです。

オペレーター:

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

これらの演算子は、同じ型の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;
}
108
Martin York

暗黙のthisパラメーターは<<- operatorの左側にあるため、メンバー関数としてはできません。 (したがって、ostream- classのメンバー関数として追加する必要があります。良くありません:)

friendingせずに、無料の機能として実行できますか?これは、これがostreamとの統合であり、クラスのコア機能ではないことが明らかになるためです。

52
Magnus Hoff

可能であれば、非メンバーおよび非フレンド機能として。

Herb SutterとScott Meyersが説明しているように、カプセル化を促進するために、メンバー関数よりも非フレンド非メンバー関数を優先します。

C++ストリームのように、場合によっては選択肢がなく、非メンバー関数を使用する必要があります。

しかし、それでも、これらの関数をクラスの友達にする必要があるわけではありません。これらの関数は、クラスアクセサーを介してクラスにアクセスできます。この方法でこれらの関数を作成することに成功すると、勝ちました。

演算子<<および>>プロトタイプについて

あなたの質問であなたが与えた例は間違っていると思います。例えば;

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

この方法がストリームでどのように機能するかを考えることさえできません。

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

タイプTのストリームのようなオブジェクトを使用するとします。

そして、タイプParagraphのオブジェクトの関連データを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)では避ける必要があります。

これが役立つことを願っています。

31
paercebal

特に最近のほとんどの場合と同様に、出力が主に診断とログ記録に使用される場合は、無料の非フレンド関数として実装する必要があります。出力に入れる必要のあるすべてのものにconstアクセサーを追加し、アウトプッターにそれらを呼び出してフォーマットを実行させます。

実際に、これらのすべてのostream出力のない関数を「ostreamhelpers」ヘッダーと実装ファイルに収集しました。これにより、その2次機能がクラスの本来の目的から遠く離れた状態に保たれます。

10
XPav

完成のために、実際にcan演算子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;
}

だからそれを要約するために-あなたはそれを行うことができますが、あなたはおそらくそうすべきではありません:)

2
ashrasmun

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が左側にあるため、フレンド関数になります。したがって、これはクラスのメンバー関数にはできません。フレンド関数にしかできません。

0

フレンド演算子=クラスと同等の権利

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