Come sovraccaricare correttamente l'operatore << per un ostream?


237

Sto scrivendo una piccola libreria di matrici in C ++ per operazioni con matrici. Tuttavia il mio compilatore si lamenta, mentre prima non lo faceva. Questo codice è stato lasciato su uno scaffale per 6 mesi e nel frattempo ho aggiornato il mio computer da debian etch a lenny (g ++ (Debian 4.3.2-1.1) 4.3.2) ma ho lo stesso problema su un sistema Ubuntu con lo stesso g ++ .

Ecco la parte rilevante della mia classe di matrice:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

E la "realizzazione":

using namespace Math;

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

    [...]

}

Questo è l'errore dato dal compilatore:

matrix.cpp: 459: errore: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' deve prendere esattamente un argomento

Sono un po 'confuso da questo errore, ma poi il mio C ++ è diventato un po' arrugginito dopo aver fatto molta Java in quei 6 mesi. :-)

Risposte:


127

Hai dichiarato la tua funzione come friend. Non è un membro della classe. È necessario rimuovere Matrix::dall'implementazione. friendsignifica che la funzione specificata (che non è un membro della classe) può accedere alle variabili dei membri privati. Il modo in cui hai implementato la funzione è come un metodo di istanza per la Matrixclasse che è sbagliato.


7
E dovresti anche dichiararlo nello spazio dei nomi Math (non solo con uno spazio dei nomi Math).
David Rodríguez - dribeas,

1
Perché operator<<deve essere nello spazio dei nomi di Math? Sembra che dovrebbe essere nello spazio dei nomi globale. Sono d'accordo che il mio compilatore vuole che sia nello spazio dei nomi di Math, ma per me non ha senso.
Mark Lakata,

Spiacenti, ma non riesco a capire perché utilizziamo la parola chiave friend qui allora? Quando si dichiara che l'operatore amico ha la precedenza su una classe, sembra che non possiamo implementare con Matrix :: operator << (ostream & os, const Matrix & m). Invece dobbiamo solo usare l'operatore globale di override dell'operatore << ostream & os, const Matrix & m) quindi perché preoccuparsi di dichiararlo all'interno della classe in primo luogo?
Patrick,

139

Ti sto solo raccontando un'altra possibilità: mi piace usare definizioni di amici per questo:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

La funzione verrà automaticamente indirizzata nello spazio dei nomi circostante Math(anche se la sua definizione appare nell'ambito di quella classe) ma non sarà visibile se non si chiama l'operatore << con un oggetto Matrix che farà sì che la ricerca dipendente dall'argomento trovi quella definizione di operatore. Questo a volte può aiutare con chiamate ambigue, poiché è invisibile per tipi di argomento diversi da Matrix. Quando scrivi la sua definizione, puoi anche fare riferimento direttamente ai nomi definiti in Matrix e alla stessa Matrix, senza qualificare il nome con qualche prefisso possibilmente lungo e fornire parametri del modello come Math::Matrix<TypeA, N>.


77

Per aggiungere alla risposta Mehrdad,

namespace Math
{
    class Matrix
    {
       public:

       [...]


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

Nella tua implementazione

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

4
Non capisco perché si tratti di un voto negativo, questo chiarisce che è possibile dichiarare l'operatore nello spazio dei nomi e nemmeno come amico e come è possibile dichiarare l'operatore.
Kal

2
La risposta di Mehrdad non aveva alcun frammento di codice, quindi ho appena aggiunto cosa potrebbe funzionare spostandolo all'esterno della classe nello spazio dei nomi stesso.
Kal

Capisco il tuo punto, ho solo guardato il tuo secondo frammento. Ma ora vedo che hai portato l'operatore fuori dalla classe. Grazie per il suggerimento
Matthias van der Vlies,

7
Non solo è fuori dalla classe, ma che sia correttamente definita all'interno del namespace Math. Inoltre ha il vantaggio aggiuntivo (forse non per una matrice, ma con altre classi) che "stampa" può essere virtuale e quindi la stampa avverrà al livello di eredità più derivato.
David Rodríguez - dribeas,

68

Supponendo che stiamo parlando di sovraccarico operator <<per tutte le classi derivate dalla std::ostreamgestione della Matrixclasse (e non di sovraccarico <<per la Matrixclasse), ha più senso dichiarare la funzione di sovraccarico al di fuori dello spazio dei nomi Math nell'intestazione.

Utilizzare una funzione amico solo se la funzionalità non può essere raggiunta tramite le interfacce pubbliche.

Matrix.h

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

Si noti che il sovraccarico dell'operatore è dichiarato fuori dallo spazio dei nomi.

Matrix.cpp

using namespace Math;
using namespace std;

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

D'altra parte, se la funzione di sovraccarico non ha bisogno di essere fatto un amico ha bisogno cioè l'accesso ai membri privati e protetti.

math.h

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

È necessario racchiudere la definizione della funzione con un blocco dello spazio dei nomi anziché solo 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

In C ++ 14 puoi usare il seguente modello per stampare qualsiasi oggetto che ha una const T :: print (std :: ostream &); membro.

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

In C ++ 20 è possibile utilizzare Concetti.

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; 
} 

soluzione interessante! Una domanda: dove dovrebbe essere dichiarato questo operatore, come in un ambito globale? Presumo che dovrebbe essere visibile a tutti i tipi che possono essere utilizzati per modellizzarlo?
Barney,

@barney Potrebbe essere nel tuo spazio dei nomi insieme alle classi che lo usano.
Quentin

non puoi semplicemente tornare std::ostream&, dato che è comunque il tipo di reso?
Jean-Michaël Celerier

5
@ Jean-MichaëlCelerier Il decltype si assicura che questo operatore venga utilizzato solo quando è presente t :: print. Altrimenti tenterebbe di compilare il corpo della funzione e dare un errore di compilazione.
Quentin

Aggiunta la versione di Concepts, testata qui godbolt.org/z/u9fGbK
QuentinUK
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.