[セットトップ]C++Primer学習ノート_59_フル・オペレータと変換-入力/出力、算術/リレーションシップ・オペレータ

7977 ワード

リロードオペレータと変換
--入力/出力、算術/リレーションシップオペレータ
I/O操作をサポートするクラスが提供するI/O操作インタフェースは、一般的に標準ライブラリiostreamが内蔵タイプで定義したインタフェースと同じであるべきであるため、多くのクラスで入力オペレータと出力オペレータを再ロードする必要がある.
一、出力オペレータ<<のリロード
IO標準ライブラリと一致するために、オペレータはostream&を最初のパラメータとして受け入れ、クラスタイプconstオブジェクトの参照を2番目のパラメータとして受け入れ、ostreamパラメータの参照を返します!
ostream &operator<<(ostream &os,const ClassType &object)
{
    os << //....
    return os;
}

1、Sales_item出力オペレータ
ostream &operator<<(ostream &out,const Sales_item &object)
{
    out << object.isbn << '\t' << object.units_sold << '\t'
        << object.revenue << '\t' << object.avg_price();

    return out;
}

2、出力オペレータは通常、できるだけ少なくフォーマットすべきである
一般的に、出力オペレータはオブジェクトの内容を出力し、改行を出力すべきではありません.オペレータのフォーマットを最小限に抑え、出力の詳細をユーザー自身で制御できます.
    Sales_item item("C++ Primer");
    cout << item << endl;	//           

3、IOオペレータは非メンバー関数でなければならない
このオペレータをクラスのメンバーとして定義することはできません.そうしないと、左オペレータはこのタイプのオブジェクトのみです.
ostream &Sales_item::operator<<(ostream &out)
{
    out << isbn << '\t' << units_sold << '\t'
        << revenue << '\t' << avg_price();

    return out;
}
//  
    Sales_item item("C++ Primer");
    //               
    item << cout << endl;
    //OR
    item.operator<<(cout);

    //Error
    cout << item << endl;

通常の使い方をサポートするには、左の操作数はostreamタイプでなければなりません.これは、オペレータがクラスのメンバーである場合、ostreamクラスのメンバーである必要がありますが、ostreamクラスは標準ライブラリの構成部分であり、標準ライブラリ内のクラスにメンバーを追加することはできません.
IOオペレータは通常、非公開データメンバーを読み書きするため、クラスは通常、IOオペレータを友元に設定します.
//P437   14.7
class CheckoutRecord
{
    friend ostream &operator<<(ostream &os,const CheckoutRecord &object);

public:
    typedef unsigned Date;
    //...

private:
    double book_id;
    string title;
    Date date_borrowed;
    Date date_due;
    pair<string,string> borrower;
    vector< pair<string,string> * > wait_list;
};

ostream &operator<<(ostream &os,const CheckoutRecord &obj)
{
    os << obj.book_id << ": " << obj.title << '\t' << obj.date_borrowed
       << '\t' << obj.date_due << '\t' << obj.borrower.first << ' '
       << obj.borrower.second << endl;

    os << "Wait_list:" << endl;
    for (vector< pair<string,string> * >::const_iterator iter = obj.wait_list.begin();
            iter != obj.wait_list.end(); ++iter)
    {
        os << (*iter) -> first << '\t' << (*iter) -> second << endl;
    }
}

二、入力オペレータ>>のリロード
出力オペレータと同様に、入力オペレータの最初のパラメータは、読み取りたいストリームを指し、同じストリームへの参照を返します.2番目のパラメータは、オペレータを入力する目的でこのオブジェクトにデータを読み込むため、読み込むオブジェクトの非const参照です.
入力オペレータはエラーとファイルの終了の可能性を処理しなければなりません!
1、Sales_itemの入力オペレータ
istream &operator>>(istream &in,Sales_item &s)
{
    double price;
    in >> s.isbn >> s.units_sold >> price;
    if (in)
    {
        s.revenue = price * s.units_sold;
    }
    else
    {
        //      ,              
        s = Sales_item();
    }

    return in;
}

2、入力期間のエラー
発生する可能性のあるエラーは次のとおりです.
1)任意の読み取り操作は、指定された値が正しくないために失敗する可能性があります.たとえばisbnを読み込むと、入力オペレータは、次の2つが数値型データであることを期待します.数値以外のデータを入力すると、今回の読み込みとストリームの後続使用は失敗します.
2)任意の読み込みは、入力ストリーム内のファイルの終了またはその他のエラーに遭遇する可能性があります.
しかし、読み込みのたびにチェックする必要はありません.読み込みデータを使用する前に一度チェックすればいいです.
    if (in)
    {
        s.revenue = price * s.units_sold;
    }
    else
    {
        s = Sales_item();
    }

エラーが発生したら、どの入力が失敗したのか気にする必要はありません.逆に、オブジェクト全体をリセットします.
3、入力エラーの処理
入力オペレータが入力に失敗したことを検出した場合は、オブジェクトが使用可能で一貫した状態にあることを確認するのが良い方法です.オブジェクトがエラーが発生する前に一部の情報を書き込んでいる場合は、特に重要です.
たとえばSales_itemの入力オペレータには、新しいisbnが正常に読み込まれ、ストリームエラーに遭遇する可能性があります.isbnを読み込んだ後にエラーが発生したのは、古いオブジェクトのunits_を意味します.soldとrevenueのメンバーは変わらず、結果として別のisbnがそのデータに関連付けられます(悲劇的です...).したがって、パラメータを空のSales_itemオブジェクトに復元することで、無効な状態を避けることができます!
【ベストプラクティス】
入力オペレータを設計する際には、可能であればエラー復旧策を決定することが重要です.
4、間違いを指摘する
入力オペレータは、発生する可能性のあるエラーの処理に加えて、入力パラメータの条件状態を設定する必要がある場合があります.
入力オペレータの中には、追加のチェックが必要なものもあります.たとえば、入力オペレータは、isbn形式が適切であるかどうかを確認できます.データの読み取りに成功したかもしれませんが、これらのデータはISBNとして適切に解釈できません.この場合、技術的には実際のIOは成功していますが、入力オペレータは失敗を指摘するために条件状態を設定する必要がある場合があります.通常、入力オペレータはfailbitを設定するだけです.eofbitの設定はファイルが枯渇したことを意味し、badbitの設定はストリームが破壊されたことを指摘することができ、これらのエラーはIO標準ライブラリ自身に残して指摘することが望ましい.
//P439   14.11
class CheckoutRecord
{
    friend istream &operator>>(istream &in,CheckoutRecord &object);

public:
    typedef unsigned Date;
    //...

private:
    double book_id;
    string title;
    Date date_borrowed;
    Date date_due;
    pair<string,string> borrower;
    vector< pair<string,string> * > wait_list;
};

istream &operator>>(istream &in,CheckoutRecord &obj)
{
    in >> obj.book_id >> obj.title >> obj.date_borrowed >> obj.date_due;
    in >> obj.borrower.first >> obj.borrower.second;
    if (!in)
    {
        obj = CheckoutRecord();
        return in;
    }

    obj.wait_list.clear();
    while (in)
    {
        pair<string,string> *p = new pair<string,string>;
        in >> p -> first >> p -> second;
        if (in)
        {
            obj.wait_list.push_back(p);
            delete p;
        }
    }

    return in;
}

三、算術演算子
一般に、算術オペレータとリレーショナルオペレータは、非メンバー関数として定義されます.
Sales_item operator+(const Sales_item &lhs,const Sales_item &rhs)
{
    Sales_item ret(lhs);
    //  Sales_item           rhs  
    ret += rhs;
    return ret;
}

加算オペレータはオペランドの状態を変更せず、オペランドはconstオブジェクトへの参照です.
【ベストプラクティス】
組み込みオペレータと一致するように、加算は参照ではなく右の値を返します.
算術オペレータを定義するとともに、複合賦値オペレータに関連するクラスも定義します.一般的には、複合賦値を使用して算術オペレータを実現する必要があります.
//P440   14.12
Sales_item operator+(const Sales_item &lhs,const Sales_item &rhs)
{
    Sales_item tmp;
    tmp.units_sold = lhs.units_sold + rhs.units_sold;
    tmp.revenue = lhs.revenue + rhs.revenue;

    return tmp;
}
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
    *this = *this + rhs;

    return *this;
}

四、関係演算子
1、等しい演算子
すべての対応するメンバーが等しい場合は、2つのオブジェクトが等しいとみなされます.
inline
bool operator==(const Sales_item &lhs,const Sales_item &rhs)
{
    return lhs.revenue == rhs.revenue && lhs.units_sold == rhs.units_sold &&
           lhs.same_isbn(rhs);
}

inline
bool operator!=(const Sales_item &lhs,const Sales_item &rhs)
{
    return !(lhs == rhs);
}

1)クラスに==オペレータが定義されている場合、2つのオブジェクトに同じデータが含まれていることを意味します.
2)クラスにアクションがある場合、このタイプの2つのオブジェクトが等しいかどうかを決定できます.通常、名前付き関数を作成するのではなくoperator==として定義されます.ユーザは==でオブジェクトを比較することに慣れ、新しい名前を覚えるよりも簡単になります.
3)クラスがoperator==を定義している場合は、operator!=も定義する必要があります.ユーザは、オペレータを使用できる場合、もう1つのオペレータも存在することを期待します.
4)等しいオペレータとそうでないオペレータは、一般的に相互に関連して定義され、あるオペレータが比較オブジェクトの実際の作業を完了するようにしなければならないが、別のオペレータは前者を呼び出すだけである.
operator==を定義したクラスは、標準ライブラリとともに使用しやすいです.findなどのアルゴリズムでは、デフォルトで==オペレータが使用されます.クラスが==を定義している場合、これらのアルゴリズムは特別な処理を必要とせずにクラスタイプに使用できます.
2、関係オペレータ
等しいオペレータを定義したクラスには、一般にリレーショナルオペレータもあります.特に、関連コンテナといくつかのアルゴリズムではオペレータ(<)よりも小さいアルゴリズムが使用されるため、operator<を定義することは非常に有用である可能性があります.
<の論理定義が==の論理定義と一致しないため、<を定義しないほうがよい.
【コメント】
コンテナといくつかのアルゴリズムを関連付けて、デフォルトでは<オペレータを使用します(ここでは翻訳者の翻訳が間違っていると思います.原文:...usethe