Comment surcharger correctement l'opérateur << pour un ostream?

237

J'écris une petite bibliothèque matricielle en C ++ pour les opérations matricielles. Cependant, mon compilateur se plaint, alors que ce n'était pas le cas auparavant. Ce code a été laissé sur une étagère pendant 6 mois et entre les deux, j'ai mis à niveau mon ordinateur de debian etch à lenny (g ++ (Debian 4.3.2-1.1) 4.3.2) mais j'ai le même problème sur un système Ubuntu avec le même g ++ .

Voici la partie pertinente de ma classe de matrice:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

Et la "mise en œuvre":

using namespace Math;

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

    [...]

}

C'est l'erreur donnée par le compilateur:

matrix.cpp: 459: erreur: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' doit prendre exactement un argument

Je suis un peu confus par cette erreur, mais là encore, mon C ++ est devenu un peu rouillé après avoir fait beaucoup de Java ces 6 mois. :-)

Matthias van der Vlies
la source

Réponses:

127

Vous avez déclaré votre fonction comme friend. Ce n'est pas un membre de la classe. Vous devez supprimer Matrix::de l'implémentation. friendsignifie que la fonction spécifiée (qui n'est pas membre de la classe) peut accéder aux variables membres privées. La façon dont vous avez implémenté la fonction est comme une méthode d'instance pour la Matrixclasse qui est incorrecte.

Mehrdad Afshari
la source
7
Et vous devez également le déclarer à l'intérieur de l'espace de noms Math (pas seulement avec un espace de noms Math).
David Rodríguez - dribeas
1
Pourquoi le operator<<doit-il être dans l'espace de noms de Math? Il semble que cela devrait être dans l'espace de noms global. Je suis d'accord que mon compilateur veut que ce soit dans l'espace de noms de Math, mais cela n'a pas de sens pour moi.
Mark Lakata
Désolé, mais je ne vois pas pourquoi utilisons-nous le mot-clé ami ici alors? Lorsque déclarer un opérateur ami remplace dans une classe, il semble que nous ne pouvons pas implémenter avec Matrix :: operator << (ostream & os, const Matrix & m). Au lieu de cela, nous devons simplement utiliser l'opérateur de remplacement de l'opérateur global << ostream & os, const Matrix & m), alors pourquoi se donner la peine de le déclarer à l'intérieur de la classe en premier lieu?
Patrick
139

Je vous parle simplement d'une autre possibilité: j'aime utiliser les définitions d'amis pour cela:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

La fonction sera automatiquement ciblée dans l'espace de noms environnant Math(même si sa définition apparaît dans le cadre de cette classe) mais ne sera visible que si vous appelez l'opérateur << avec un objet Matrix qui rendra la recherche dépendante de l'argument trouver cette définition d'opérateur. Cela peut parfois aider avec des appels ambigus, car il est invisible pour les types d'arguments autres que Matrix. Lors de l'écriture de sa définition, vous pouvez également vous référer directement aux noms définis dans Matrix et à Matrix lui-même, sans qualifier le nom avec un préfixe éventuellement long et fournir des paramètres de modèle comme Math::Matrix<TypeA, N>.

Johannes Schaub - litb
la source
77

Pour ajouter à la réponse de Mehrdad,

namespace Math
{
    class Matrix
    {
       public:

       [...]


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

Dans votre implémentation

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }
Kal
la source
4
Je ne comprends pas pourquoi c'est un vote négatif, cela clarifie que vous pouvez déclarer que l'opérateur est dans l'espace de noms et pas même en tant qu'ami et comment vous pouvez éventuellement déclarer l'opérateur.
kal
2
La réponse de Mehrdad n'avait aucun extrait de code, j'ai donc ajouté ce qui pourrait fonctionner en le déplaçant en dehors de la classe dans l'espace de noms lui-même.
kal
Je comprends votre point, je n'ai regardé que votre deuxième extrait. Mais maintenant, je vois que vous avez fait sortir l'opérateur de la classe. Merci pour la suggestion.
Matthias van der Vlies le
7
Non seulement il est hors de la classe, mais il est correctement défini dans l'espace de noms Math. Il a également l'avantage supplémentaire (peut-être pas pour une matrice, mais avec d'autres classes) que «l'impression» peut être virtuelle et donc l'impression se fera au niveau d'héritage le plus dérivé.
David Rodríguez - dribeas
68

En supposant que nous parlons de surcharge operator <<pour toutes les classes dérivées de std::ostreampour gérer la Matrixclasse (et non de surcharge <<pourMatrix classe), il est plus logique de déclarer la fonction de surcharge en dehors de l'espace de noms Math dans l'en-tête.

Utilisez une fonction ami uniquement si la fonctionnalité ne peut pas être obtenue via les interfaces publiques.

Matrix.h

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

Notez que la surcharge de l'opérateur est déclarée en dehors de l'espace de noms.

Matrix.cpp

using namespace Math;
using namespace std;

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

En revanche, si votre fonction de surcharge ne doit être fait un ami : besoins d' accès aux membres privés et protégés.

Math.h

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

Vous devez entourer la définition de fonction d'un bloc d'espace de noms au lieu de simplement 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;
    }                 
}
sanjivr
la source
38

En C ++ 14, vous pouvez utiliser le modèle suivant pour imprimer tout objet qui a une const T :: print (std :: ostream &); membre.

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

En C ++ 20, les concepts peuvent être utilisés.

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; 
} 
QuentinUK
la source
solution intéressante! Une question - où cet opérateur doit être déclaré, comme dans une portée globale? Je suppose qu'il devrait être visible par tous les types qui peuvent être utilisés pour le modéliser?
barney
@barney Il pourrait être dans votre propre espace de noms avec les classes qui l'utilisent.
QuentinUK
ne pouvez-vous pas simplement revenir std::ostream&, puisque c'est le type de retour de toute façon?
Jean-Michaël Celerier
5
@ Jean-MichaëlCelerier Le decltype s'assure que cet opérateur n'est utilisé que lorsque t :: print est présent. Sinon, il tenterait de compiler le corps de la fonction et donnerait une erreur de compilation.
QuentinUK
Version des concepts ajoutée, testée ici godbolt.org/z/u9fGbK
QuentinUK