operator <<는 친구 또는 멤버 함수로 구현해야합니까?


129

그것이 기본적으로 질문입니다. "올바른"구현 방법이 operator<<있습니까? 이것을 읽으면 다음과 같은 것을 볼 수 있습니다.

friend bool operator<<(obj const& lhs, obj const& rhs);

같은 것보다 선호됩니다

ostream& operator<<(obj const& rhs);

하지만 왜 둘 중 하나를 사용해야하는지 잘 모르겠습니다.

내 개인 사례는 다음과 같습니다.

friend ostream & operator<<(ostream &os, const Paragraph& p) {
    return os << p.to_str();
}

그러나 나는 아마도 할 수 있습니다.

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

이 결정의 근거는 무엇입니까?

참고 :

 Paragraph::to_str = (return paragraph) 

여기서 단락은 문자열입니다.


4
BTW 당신은 ​​아마도 멤버 함수의 서명에 const를 추가해야합니다
— Motti

4
operator <<에서 bool을 반환하는 이유는 무엇입니까? 스트림 연산자 또는 비트 시프트의 과부하로 사용하고 있습니까?
— Martin York

답변:


120

여기서 문제는 링크 하는 기사에 대한 해석에 있습니다 .

평등

이 문서는 bool 관계 연산자를 올바르게 정의하는 데 문제가있는 사람에 대한 것입니다.

운영자 :

  • 같음 == 및! =
  • 관계 <> <=> =

이러한 연산자는 동일한 유형의 두 개체를 비교할 때 부울을 반환해야합니다. 일반적으로 이러한 연산자를 클래스의 일부로 정의하는 것이 가장 쉽습니다. 이는 클래스가 자동으로 자신의 친구가되므로 Paragraph 유형의 개체가 서로를 검사 할 수 있기 때문입니다 (서로 개인 멤버도 포함).

이러한 독립 함수를 만들기위한 인수가 있습니다. 이렇게하면 자동 변환이 동일한 유형이 아닌 경우 양쪽을 모두 변환 할 수 있고 멤버 함수는 rhs 만 자동 변환 할 수 있습니다. 나는 당신이 처음에 자동 변환이 일어나기를 정말로 원하지 않기 때문에 이것을 종이 남자의 주장이라고 생각합니다 (보통). 그러나 이것이 당신이 원하는 것이라면 (권장하지 않습니다) 비교기를 독립적으로 만드는 것이 유리할 수 있습니다.

스트리밍

스트림 연산자 :

  • 연산자 << 출력
  • 연산자 >> 입력

이진 시프트가 아닌 스트림 연산자로 사용할 때 첫 번째 매개 변수는 스트림입니다. 스트림 객체에 대한 액세스 권한이 없기 때문에 (수정할 수있는 것이 아님) 멤버 연산자가 될 수 없으며 클래스 외부에 있어야합니다. 따라서 그들은 클래스의 친구이거나 스트리밍을 수행 할 공용 메서드에 액세스 할 수 있어야합니다.

또한 이러한 개체가 스트림 개체에 대한 참조를 반환하여 스트림 작업을 함께 연결할 수 있습니다.

#include <iostream>

class Paragraph
{
    public:
        explicit Paragraph(std::string const& init)
            :m_para(init)
        {}

        std::string const&  to_str() const
        {
            return m_para;
        }

        bool operator==(Paragraph const& rhs) const
        {
            return m_para == rhs.m_para;
        }
        bool operator!=(Paragraph const& rhs) const
        {
            // Define != operator in terms of the == operator
            return !(this->operator==(rhs));
        }
        bool operator<(Paragraph const& rhs) const
        {
            return  m_para < rhs.m_para;
        }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};

std::ostream & operator<<(std::ostream &os, const Paragraph& p)
{
    return os << p.to_str();
}


int main()
{
    Paragraph   p("Plop");
    Paragraph   q(p);

    std::cout << p << std::endl << (p == q) << std::endl;
}

19
왜 operator<< private:?
— Matt Clarkson

47
@MattClarkson : 아닙니다. 따라서 friend 함수 선언은 클래스의 일부가 아니므로 액세스 지정자의 영향을받지 않습니다. 일반적으로 액세스하는 데이터 옆에 친구 함수 선언을 넣습니다.
— Martin York

12
공용 기능을 사용하여 데이터에 액세스하는 경우 친숙한 기능이 필요한 이유는 무엇입니까? 질문이 어리 석다면 죄송합니다.
— Semyon Danilov 2014 년

4
@SemyonDanilov : 캡슐화를 중단하고 게터를 추가하는 이유는 무엇입니까? freiend캡슐화를 중단하지 않고 공용 인터페이스를 확장하는 방법입니다. programmers.stackexchange.com/a/99595/12917
— Martin York

3
@LokiAstari 그러나 확실히 그것은 to_str을 제거하거나 그것을 비공개로 만드는 것에 대한 논쟁입니다. 현재 스트리밍 운영자는 공개 기능 만 사용하므로 친구가 될 필요가 없습니다.
— deworde

53

암시 적 this매개 변수가 <<-operator 의 왼쪽 이기 때문에 멤버 함수로 수행 할 수 없습니다 . (따라서 ostream-class에 멤버 함수로 추가해야합니다 . 좋지 않습니다. :)

없이 무료 기능으로 할 수 있습니까? friend 있습니까? 이것이 제가 선호하는 것입니다. 이것이 ostream클래스의 핵심 기능이 아니라 와의 통합임을 분명히하기 때문 입니다.


1
"반의 핵심 기능이 아닙니다." 그것이 "친구"가 의미하는 것입니다. 핵심 기능이라면 친구가 아닌 클래스에있을 것입니다.
— xaxxon jul.

1
@xaxxon 내 첫 번째 문장이이 경우 함수를 멤버 함수로 추가하는 것이 불가능한 이유를 설명한다고 생각합니다. friend함수는 멤버 함수와 동일한 권한을 (가지고 이 무엇인가 friend가 그 필요 왜 그렇게 클래스의 사용자로, 내가 궁금해 할 것, 의미). 이것이 제가 "핵심 기능"이라는 표현으로 만들고자하는 구별입니다.
— Magnus Hoff

32

가능하다면 비회원 및 친구가 아닌 기능으로 활동합니다.

Herb Sutter와 Scott Meyers가 설명했듯이 캡슐화를 늘리기 위해 멤버 함수보다 친구가 아닌 비 멤버 함수를 선호합니다.

C ++ 스트림과 같은 일부 경우에는 선택권이 없으며 비 멤버 함수를 사용해야합니다.

그러나 그렇다고해서 이러한 함수를 클래스의 친구로 만들어야한다는 의미는 아닙니다. 이러한 함수는 클래스 접근자를 통해 클래스에 액세스 할 수 있습니다. 이런 식으로 이러한 함수를 작성하는 데 성공하면 이겼습니다.

운영자 << 및 >> 프로토 타입 정보

귀하의 질문에 제시 한 예가 잘못되었다고 생각합니다. 예를 들면 다음과 같습니다.

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

이 방법이 스트림에서 어떻게 작동 할 수 있는지 생각조차 할 수 없습니다.

다음은 << 및 >> 연산자를 구현하는 두 가지 방법입니다.

T 유형의 스트림과 유사한 객체를 사용한다고 가정 해 보겠습니다.

그리고 Paragraph 유형의 객체 관련 데이터를 T에서 추출 / 삽입하려고합니다.

일반 연산자 << 및 >> 함수 프로토 타입

첫 번째 기능은 다음과 같습니다.

// T << Paragraph
T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// T >> Paragraph
T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return p_oInputStream ;
}

일반 연산자 << 및 >> 메서드 프로토 타입

두 번째는 방법으로서 :

// T << Paragraph
T & T::operator << (const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return *this ;
}

// T >> Paragraph
T & T::operator >> (const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return *this ;
}

이 표기법을 사용하려면 T의 클래스 선언을 확장해야합니다. STL 객체의 경우 이것은 불가능합니다 (수정해서는 안됩니다 ...).

T가 C ++ 스트림이면 어떻게 될까요?

다음은 C ++ 스트림에 대한 동일한 << 및 >> 연산자의 프로토 타입입니다.

일반 basic_istream 및 basic_ostream의 경우

스트림의 경우 C ++ 스트림을 수정할 수 없으므로 함수를 구현해야합니다. 이는 다음과 같은 의미입니다.

// OUTPUT << Paragraph
template <typename charT, typename traits>
std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> Paragraph
template <typename charT, typename traits>
std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & p_oInputStream, const CMyObject & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

char istream 및 ostream의 경우

다음 코드는 문자 기반 스트림에서만 작동합니다.

// OUTPUT << A
std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> A
std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Rhys Ulerich는 문자 기반 코드가 그 위에있는 일반 코드의 "특수화"에 불과하다는 사실에 대해 언급했습니다. 물론 Rhys가 맞습니다. 문자 기반 예제를 사용하는 것은 권장하지 않습니다. 읽기가 더 간단하기 때문에 여기에만 제공됩니다. char 기반 스트림으로 만 작업하는 경우에만 실행 가능하므로 wchar_t 코드가 일반적인 플랫폼 (예 : Windows)에서는 피해야합니다.

이것이 도움이되기를 바랍니다.


일반 basic_istream 및 basic_ostream 템플릿 코드가 이미 std :: ostream- 및 std :: istream 특정 버전을 포함하지 않습니까? 후자의 두 가지는 문자를 사용하는 전자의 인스턴스 화일 뿐이 기 때문입니다.
— Rhys Ulerich 2009

@Rhys Ulerich : 물론입니다. Windows에서는 char 및 wchar_t 코드를 모두 처리해야하기 때문에 일반 템플릿 버전 만 사용합니다. 두 번째 버전의 유일한 장점은 첫 번째 버전보다 더 단순하게 나타나는 것입니다. 그것에 대한 내 게시물을 명확히 할 것입니다.
— paercebal 2009

10

특히 요즘 대부분의 경우와 같이 출력이 주로 진단 및 로깅에 사용되는 경우 친구가 아닌 무료 기능으로 구현해야합니다. 출력에 필요한 모든 것에 대한 const 접근자를 추가 한 다음 출력자가 해당 접근자를 호출하고 형식을 지정하도록합니다.

실제로 "ostreamhelpers"헤더 및 구현 파일에서 이러한 모든 ostream 출력 무료 함수를 수집했습니다.이 기능은 클래스의 실제 목적과는 거리가 먼 보조 기능을 유지합니다.


7

서명 :

bool operator<<(const obj&, const obj&);

이것은 오히려 의심 맞지 않는 것 같다 stream가 연산자 오버로딩 학대의 경우처럼 보이는 있도록 규칙이나 비트 규칙을 operator <반환해야 bool하지만,operator << 해야 아마도 다른 것을 반환해야합니다.

그렇게 의미했다면 다음과 같이 말하십시오.

ostream& operator<<(ostream&, const obj&); 

그런 다음 ostream필요에 따라 함수를 추가 할 수 없기 때문에 함수는 액세스해야하는 항목에 따라 자유 함수 여야합니다 friend(비공개 멤버 또는 보호 된 멤버에 액세스 할 필요가없는 경우 만들 필요가 없습니다). 친구).


주문을 ostream사용할 때 수정 권한 이 필요하다는 점을 언급 할 가치 가 있습니다 ostream.operator<<(obj&). 따라서 자유 기능. 그렇지 않으면 사용자 유형은 액세스를 수용하기 위해 스팀 유형이어야합니다.
— wulfgarpro

2

완료를 위해 실제로 연산자를 만들 수 있음을 추가하고 싶습니다.ostream& operator << (ostream& os) 클래스 내에서 . 내가 아는 바에 따르면 사용하는 것은 좋지 않습니다. 왜냐하면 매우 복잡하고 직관적이지 않기 때문입니다.

다음 코드가 있다고 가정 해 보겠습니다.

#include <iostream>
#include <string>

using namespace std;

struct Widget
{
    string name;

    Widget(string _name) : name(_name) {}

    ostream& operator << (ostream& os)
    {
        return os << name;
    }
};

int main()
{
    Widget w1("w1");
    Widget w2("w2");

    // These two won't work
    {
        // Error: operand types are std::ostream << std::ostream
        // cout << w1.operator<<(cout) << '\n';

        // Error: operand types are std::ostream << Widget
        // cout << w1 << '\n';
    }

    // However these two work
    {
        w1 << cout << '\n';

        // Call to w1.operator<<(cout) returns a reference to ostream&
        w2 << w1.operator<<(cout) << '\n';
    }

    return 0;
}

그래서 요약하자면-할 수는 있지만 그렇게해서는 안됩니다. :)


0

친구 연산자 = 클래스와 동등한 권리

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}

0

operator<< 친구 기능으로 구현 :

#include <iostream>
#include <string>
using namespace std;

class Samp
{
public:
    int ID;
    string strName; 
    friend std::ostream& operator<<(std::ostream &os, const Samp& obj);
};
 std::ostream& operator<<(std::ostream &os, const Samp& obj)
    {
        os << obj.ID<< “ ” << obj.strName;
        return os;
    }

int main()
{
   Samp obj, obj1;
    obj.ID = 100;
    obj.strName = "Hello";
    obj1=obj;
    cout << obj <<endl<< obj1;

} 

출력 :
100 Hello
100 Hello

이는 객체가 오른쪽에 operator<<있고 인수 cout가 왼쪽에 있기 때문에 친구 함수가 될 수 있습니다 . 따라서 이것은 클래스의 멤버 함수가 될 수 없으며 친구 함수 만 될 수 있습니다.


멤버 기능으로 쓸 방법이 없을 것 같아 !!
— Rohit Vipin Mathews

왜 모든 것이 대담합니다. 제거하겠습니다.
— Sebastian Mach
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.