Як правильно перевантажувати оператор << для потоку?


237

Я пишу невелику бібліотеку матриць на C ++ для матричних операцій. Однак мій упорядник скаржиться, де раніше цього не робив. Цей код був залишений на полиці протягом 6 місяців, і між ними я модернізував комп’ютер з debian etch до lenny (g ++ (Debian 4.3.2-1.1) 4.3.2), проте у мене є та ж проблема в системі Ubuntu з тим же g ++ .

Ось відповідна частина мого матричного класу:

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: error: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' повинен приймати рівно один аргумент

Я трохи збентежена цією помилкою, але потім мій C ++ став трохи іржавим після того, як робив багато Java протягом тих 6 місяців. :-)

Відповіді:


127

Ви оголосили свою функцію як friend. Це не член класу. Вам слід видалити Matrix::з реалізації. friendозначає, що зазначена функція (яка не є членом класу) може отримати доступ до змінних приватних членів. Те, як ви реалізували функцію, схожий на метод екземпляра для Matrixкласу, який є неправильним.


7
І ви також повинні оголосити його всередині простору імен Math (а не лише за допомогою простору імен Math).
Девід Родрігес - дрибес

1
Чому довідка operator<<повинна бути в просторі імен Math? Здається, що це має бути у глобальному просторі імен. Я згоден, що мій компілятор хоче, щоб він знаходився в просторі імен Math, але це не має для мене сенсу.
Марк Лаката

Вибачте, але я не розумію, чому тут ми використовуємо друге ключове слово? Коли оголошувати друг-оператор замінює клас у класі, здається, що ми не можемо реалізувати з Matrix :: operator << (ostream & os, const Matrix & m). Натомість нам потрібно просто використати глобальний оператор переосмислення оператора << ostream & os, const Matrix & m), то чому ж взагалі намагатися оголосити це всередині класу?
Патрік

139

Просто кажу вам про ще одну можливість: мені подобається використовувати для цього визначення друзів:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

Функція буде автоматично націлена на оточуючу область імен Math(навіть незважаючи на те, що її визначення відображається в межах цього класу), але вона не буде видимою, якщо ви не зателефонуєте оператору << з об'єктом Matrix, який зробить пошук, залежний від аргументів, знайде це визначення оператора. Іноді це може допомогти при неоднозначних викликах, оскільки це невидимо для типів аргументів, крім Matrix. Пишучи його визначення, ви також можете посилатися безпосередньо на імена, визначені в Matrix та на саму Matrix, не визначаючи ім'я з деяким, можливо, довгим префіксом та надаючи параметри шаблону, як-от Math::Matrix<TypeA, N>.


77

Щоб додати відповідь Мехрдада,

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
Я не розумію, чому це відхилення від голосування, це пояснює, що ви можете оголосити оператора в просторі імен, а навіть не як друга, і як ви можете оголосити оператора.
кал

2
У відповіді Мехрдада не було фрагмента коду, тому я просто додав те, що може працювати, переміщуючи його поза класом у самому просторі імен.
кал

Я розумію вашу думку, я лише подивився на ваш другий фрагмент. Але тепер я бачу, що ви вивели оператора з класу. Дякую за пропозицію.
Маттіас ван дер Вліс

7
Мало того, що з класу, але він правильно визначено в просторі імен Math. Крім того, він має додаткову перевагу (можливо, не для матриці, але для інших класів), що "друк" може бути віртуальним, і, таким чином, друк відбуватиметься на найбільш отриманому рівні успадкування.
Девід Родрігес - дрибес

68

Якщо припустити, що ми говоримо про перевантаження operator <<для всіх класів, похідних від std::ostreamобробки Matrixкласу (а не перевантаження <<для Matrixкласу), має більше сенсу оголосити функцію перевантаження поза межами простору імен Math у заголовку.

Використовуйте функцію друзів лише в тому випадку, якщо функціональність неможливо досягти за допомогою загальнодоступних інтерфейсів.

Матриця.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;
}

З іншого боку, якщо ваша функція перевантаження робить потрібно бути один то необхідний доступ до приватних і захищених членів.

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

цікаве рішення! Одне запитання - де цей оператор повинен бути оголошений, як у глобальному масштабі? Я припускаю, що це повинно бути видимим для всіх типів, які можна використовувати для його шаблонування?
barney

@barney Це може бути у вашому власному просторі імен разом з класами, які його використовують.
QuentinUK

ви не можете просто повернутися std::ostream&, оскільки це все-таки тип повернення?
Жан-Міхаель Селері

5
@ Jean-MichaëlCelerier Decltype гарантує, що цей оператор використовується лише тоді, коли присутній t :: print. В іншому випадку він спробує скомпілювати тіло функції та подати помилку компіляції.
QuentinUK

Додана версія концепцій, перевірена тут godbolt.org/z/u9fGbK
QuentinUK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.