ostream에 << 연산자를 올바르게 오버로드하는 방법은 무엇입니까?


237

행렬 작업을 위해 C ++로 작은 행렬 라이브러리를 작성하고 있습니다. 그러나 내 컴파일러는 전에는 그렇지 않은 곳에서 불평합니다. 이 코드는 6 개월 동안 선반에 있었고 컴퓨터를 데비안 에칭에서 레니 (g ++ (Debian 4.3.2-1.1) 4.3.2)로 업그레이드했지만 동일한 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 : 오류 : 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)'는 정확히 하나의 인수를 가져야합니다.

이 오류로 인해 약간 혼란 스럽지만 6 개월 동안 Java를 많이 수행 한 후에도 C ++이 약간 녹슬 었습니다. :-)

답변:


127

함수를로 선언했습니다 friend. 수업의 회원이 아닙니다. Matrix::구현에서 제거해야합니다 . friend지정된 함수 (클래스의 멤버가 아님)가 개인 멤버 변수에 액세스 할 수 있음을 의미합니다. 함수를 구현 한 방식 Matrix은 잘못된 클래스 의 인스턴스 메소드와 같습니다 .


7
또한 사용 네임 스페이스 Math가 아니라 Math 네임 스페이스 내에 선언해야합니다.
David Rodríguez-dribeas

1
operator<<네임 스페이스에 있어야 Math합니까? 전역 네임 스페이스에 있어야합니다. 내 컴파일러가의 네임 스페이스에 있기를 원 Math하지만 그것이 의미가 없다는 것에 동의합니다 .
Mark Lakata

죄송합니다. 그런데 왜 친구 키워드를 사용합니까? 클래스에서 친구 연산자 재정의를 선언하면 Matrix :: operator << (ostream & os, const Matrix & m)로 구현할 수없는 것 같습니다. 대신 전역 연산자 재정의 연산자 << ostream & os, const Matrix & m)를 사용해야하므로 왜 클래스 내부에서 선언해야합니까?
Patrick

139

다른 가능성에 대해 이야기하는 것만으로도 친구 정의를 사용하는 것이 좋습니다.

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 answer에 추가하려면

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

나는 당신의 요점을 이해합니다, 나는 당신의 두 번째 발췌 문장 만 보았습니다. 그러나 이제 나는 당신이 수업에서 연산자를 꺼내는 것을 보았습니다. 제안 해 주셔서 감사합니다.
Matthias van der Vlies

7
클래스 외부에있을뿐만 아니라 Math 네임 스페이스 내에 올바르게 정의 되어 있습니다. 또한 '인쇄'가 가상 일 수 있으므로 가장 파생 된 상속 수준에서 인쇄가 수행된다는 추가 이점 (매트릭스가 아닌 다른 클래스의 경우)이 있습니다.
David Rodríguez-dribeas

68

우리가 과부하에 대해 얘기하고 있다고 가정 operator <<에서 파생 된 모든 클래스에 대한 std::ostream핸들을 Matrix(오버로드가 아닌 클래스 <<에 대한 Matrix클래스), 그것은 헤더의 수학 네임 스페이스 외부에 과부하 함수를 선언 더 의미가 있습니다.

공용 인터페이스를 통해 기능을 수행 할 수없는 경우에만 친구 기능을 사용하십시오.

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

반면에, 당신의 과부하 기능은 경우 않습니다 즉 개인 보호 멤버에 대한 액세스를 필요로하는 친구를 할 필요가있다.

Math.h

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

함수 정의를 just 대신 네임 스페이스 블록으로 묶어야합니다 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&어쨌든 반환 유형이므로 반환 할 수 없습니까?
Jean-Michaël Celerier

5
@ Jean-MichaëlCelerier decltype은 t :: print가있을 때만이 연산자를 사용하도록합니다. 그렇지 않으면 함수 본문을 컴파일하려고 시도하고 컴파일 오류가 발생합니다.
QuentinUK

여기에 추가 된 개념 버전 godbolt.org/z/u9fGbK
QuentinUK
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.