Operator<< はフレンドとして実装する必要がありますか、それともメンバー関数として実装する必要がありますか?

StackOverflow https://stackoverflow.com/questions/236801

  •  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) 

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

役に立ちましたか?

解決

ここでの問題は、あなたが書いた記事の解釈にあります。 リンク.

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

オペレーター:

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

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

他のヒント

メンバー関数として実行することはできません。 this パラメータはの左側です <<-オペレーター。(したがって、これをメンバー関数として追加する必要があります。 ostream-クラス。良くない :)

なしで無料機能として実行できますか friendそれは?これが との統合であることが明確になるため、私はこれを好みます。 ostream, 、クラスのコア機能ではありません。

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

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

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

ただし、これらの関数をクラスの友達にする必要があるという意味ではありません。これらの関数は、クラス アクセサーを通じて引き続きクラスにアクセスできます。この方法でこれらの関数を書くことに成功すれば、成功です。

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

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

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

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

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

T 型のストリームのようなオブジェクトを使用したいとします。

そして、Paragraph 型のオブジェクトの関連データを T から抽出/T に挿入したいと考えています。

汎用演算子 << および >> 関数プロトタイプ

1 つ目は関数としてです。

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

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

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

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

サイン:

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

むしろ疑わしいようですが、これは当てはまりません stream 規則でもビットごとの規則でもないので、演算子のオーバーロードの悪用のケースのように見えます。 operator < 返すべきです bool しかし operator << おそらく他のものを返すはずです。

そう言うつもりなら、こう言ってください。

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

それから機能を追加することはできないので、 ostream 必然的に、関数は無料関数でなければなりません。 friend アクセスする必要があるかどうかは、アクセスする必要があるものによって異なります (プライベートまたは保護されたメンバーにアクセスする必要がない場合は、フレンドにする必要はありません)。

最後に、確かにそう付け加えておきたいと思います。 できる 演算子を作成する 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;
}

要約すると、それを行うことはできますが、おそらくやるべきではありません:)

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

フレンド演算子 = クラスとしての平等な権利

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top