ostreamの<<演算子を適切にオーバーロードする方法は?


237

行列演算のためにC ++で小さな行列ライブラリを書いています。しかし、私のコンパイラは文句を言いますが、以前はそうではありませんでした。このコードは6か月間棚に置いたままで、その間にコンピュータをdebian etchからlenny(g ++(Debian 4.3.2-1.1)4.3.2)にアップグレードしましたが、同じg ++のUbuntuシステムでも同じ問題があります。

これが私のマトリックスクラスの関連部分です:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix);
    }
}

そして「実装」:

using namespace Math;

std::ostream& Matrix::operator <<(std::ostream& stream, const Matrix& matrix) {

    [...]

}

これはコンパイラーによって与えられたエラーです:

matrix.cpp:459:エラー: 'std :: ostream&Math :: Matrix :: operator <<(std :: ostream&、const Math :: Matrix&)'は引数を1つだけ取る必要があります

私はこのエラーに少し混乱していますが、この6か月間多くのJavaを実行した後で、私のC ++は少し錆びてきました。:-)

回答:


127

関数をとして宣言しましたfriend。クラスのメンバーではありません。Matrix::実装から削除する必要があります。friend指定された関数(クラスのメンバーではない)がプライベートメンバー変数にアクセスできることを意味します。あなたが関数を実装した方法Matrixは間違っているクラスのインスタンスメソッドのようなものです。


7
また、(名前空間Mathを使用するだけでなく)Math名前空間内でも宣言する必要があります。
デビッドロドリゲス-ドリベス2009年

1
operator<<がの名前空間にある必要があるのはなぜMathですか?それはグローバル名前空間にあるはずです。私のコンパイラはそれがの名前空間にあることを望んでいることに同意しますがMath、それは私には意味がありません。
Mark Lakata、2015年

申し訳ありませんが、なぜここでフレンドキーワードを使用するのかわかりません。クラスでフレンドオペレーターのオーバーライドを宣言すると、Matrix :: operator <<(ostream&os、const Matrix&m)で実装できないようです。代わりに、グローバルオペレーターオーバーライドオペレーター<< ostream&os、const Matrix&m)を使用する必要があるので、そもそもクラス内で宣言する必要があるのはなぜですか?
Patrick

139

他に1つの可能性についてお話しします。そのために友人の定義を使用するのが好きです。

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix) {
            [...]
        }
    };
}

関数は周囲の名前空間に自動的にターゲット設定されますMath(その定義はそのクラスのスコープ内に表示されます)が、引数に依存するルックアップでその演算子の定義を見つけるMatrixオブジェクトでoperator <<を呼び出さない限り、表示されません。これは、Matrix以外の引数の型では見えないため、あいまいな呼び出しに役立つ場合があります。その定義を書くとき、Matrixで定義された名前とMatrix自体を直接参照することもできます。名前をいくつかの長いプレフィックスで修飾したり、などのテンプレートパラメーターを指定したりする必要はありませんMath::Matrix<TypeA, N>


77

Mehrdadの回答に追加するには、

namespace Math
{
    class Matrix
    {
       public:

       [...]


    }   
    std::ostream& operator<< (std::ostream& stream, const Math::Matrix& matrix);
}

あなたの実装では

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }

4
なぜこれが反対票なのかわかりません。これは、演算子を名前空間に含めることを宣言でき、フレンドとしても宣言できないことと、演算子を宣言できることを示しています。
kal

2
Mehrdadの回答にはコードスニペットがなかったため、名前空間自体のクラスの外に移動することで機能するものを追加しただけです。
kal

ポイントを理解しました。2つ目のスニペットのみを確認しました。しかし今、私はあなたがクラスからオペレーターを連れ出したのを見ます。提案をありがとう。
Matthias van der Vlies、

7
クラス外であるだけでなく、Math名前空間で適切に定義さています。また、「マトリックス」ではないが他のクラスでは、「印刷」を仮想化できるという追加の利点もあるので、最も派生した継承レベルで印刷が行われます。
デビッドロドリゲス-ドリベス2009年

68

operator <<クラスstd::ostreamを処理するためにから派生したすべてのクラスのオーバーロードMatrix(およびクラスのオーバーロード<<ではないMatrix)について話していると仮定すると、ヘッダーのMath名前空間の外側でオーバーロード関数を宣言する方が理にかなっています。

フレンドインターフェイス機能は、パブリックインターフェイスを介して機能を実現できない場合にのみ使用してください。

Matrix.h

namespace Math { 
    class Matrix { 
        //...
    };  
}
std::ostream& operator<<(std::ostream&, const Math::Matrix&);

演算子のオーバーロードは名前空間の外で宣言されていることに注意してください。

Matrix.cpp

using namespace Math;
using namespace std;

ostream& operator<< (ostream& os, const Matrix& obj) {
    os << obj.getXYZ() << obj.getABC() << '\n';
    return os;
}

一方、あなたのオーバーロード機能があればつまり、プライベートおよびprotectedメンバーへのアクセスを必要とする友人を行う必要があります。

Math.h

namespace Math {
    class Matrix {
        public:
            friend std::ostream& operator<<(std::ostream&, const Matrix&);
    };
}

関数定義は、だけでなく、名前空間ブロックで囲む必要がありますusing namespace Math;

Matrix.cpp

using namespace Math;
using namespace std;

namespace Math {
    ostream& operator<<(ostream& os, const Matrix& obj) {
        os << obj.XYZ << obj.ABC << '\n';
        return os;
    }                 
}

38

C ++ 14では、次のテンプレートを使用して、T :: print(std :: ostream&)const;を持つオブジェクトを印刷できます。メンバー。

template<class T>
auto operator<<(std::ostream& os, T const & t) -> decltype(t.print(os), os) 
{ 
    t.print(os); 
    return os; 
} 

C ++ 20では、概念を使用できます。

template<typename T>
concept Printable = requires(std::ostream& os, T const & t) {
    { t.print(os) };
};

template<Printable T>
std::ostream& operator<<(std::ostream& os, const T& t) { 
    t.print(os); 
    return os; 
} 

興味深い解決策!1つの質問-グローバルスコープのように、この演算子はどこで宣言する必要がありますか?私はそれをテンプレート化するために使用できるすべてのタイプから見えるはずだと思いますか?
barney

@barneyそれはそれを使用するクラスと一緒にあなた自身の名前空間にあるかもしれません。
QuentinUK、2016年

std::ostream&それはとにかく戻り型なので、単に返すことはできませんか?
ジャンマイケルCelerier

5
@Jean-MichaëlCelerierdecltypeは、この演算子がt :: printが存在する場合にのみ使用されるようにします。そうしないと、関数本体をコンパイルしようとしてコンパイルエラーが発生します。
QuentinUK

ここでテストされた概念バージョンが追加されました godbolt.org/z/u9fGbK
QuentinUK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.