공개 친구 교환 회원 기능


169

copy-and-swap-idiom에 대한 아름다운 대답 에는 약간의 도움이 필요한 코드가 있습니다.

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

그리고 그는 메모를 추가

우리는 std :: swap을 우리 유형에 특화하고, 무료 기능 스왑과 함께 클래스 내 스왑을 제공해야한다고 주장하는 다른 주장이 있습니다. 그러나 이것은 모두 불필요합니다. 우리의 기능은 ADL을 통해 찾을 수 있습니다. 하나의 기능이 수행됩니다.

friend나는 "비우호적 인"조건에 조금 오전, 나는 인정해야합니다. 그래서 내 주요 질문은 다음과 같습니다.

  • 무료 함수처럼 보이지만 클래스 본문 안에 있습니까?
  • swap정적이지 않습니까? 분명히 멤버 변수를 사용하지 않습니다.
  • "스왑을 올바르게 사용하면 ADL을 통해 스왑을 찾을 수 있습니다" ? ADL은 네임 스페이스를 검색 할 것입니다. 그러나 수업 내부에서도 보입니까? 아니면 어디로 friend들어 오나요?

사이드 질문 :

  • C ++ 11에서는 swaps로 표시해야 noexcept합니까?
  • C ++ (11)와 함께 범위-을 위해 , 나는 배치해야합니다 friend iter begin()friend iter end()클래스 내부에 같은 방법은? 나는 friend여기서 필요하지 않다고 생각합니다 .

범위 기반에 대한 부수적 인 질문을 고려할 때 : 멤버 함수를 작성하고 std 네임 스페이스 (§24.6.5)에서 begin () 및 end ()에 대한 범위 액세스를 유지하는 것이 좋습니다. std 네임 스페이스 (§6.5.4 참조) 그러나 이러한 함수는 <iterator> 헤더의 일부라는 단점이 있습니다. 포함하지 않으면 직접 작성할 수도 있습니다.
비투스

2
friend함수가 멤버 함수가 아니기 때문에 왜 정적이지 않습니까?
aschepler

답변:


175

글을 쓰는 방법은 여러 가지가 있는데 swap, 다른 방법 보다 낫습니다. 그러나 시간이 지남에 따라 단일 정의가 가장 효과적이라는 것이 발견되었습니다. swap함수 작성에 대해 어떻게 생각할 수 있는지 생각해 봅시다 .


먼저 컨테이너와 같은 컨테이너에는 다음과 같은 std::vector<>단일 인수 멤버 함수 swap가 있습니다.

struct vector
{
    void swap(vector&) { /* swap members */ }
};

당연히 우리 수업도 그래야합니까? 글쎄,별로. 표준 라이브러리에는 모든 종류의 불필요한 것들이 있으며 멤버 swap는 그중 하나입니다. 왜? 계속하자.


우리가해야 할 일은 표준적인 것이 무엇인지, 그리고 클래스 함께 일하기 위해해야 ​​할 일을 식별하는 것입니다. 그리고 표준 스와핑 방법은입니다 std::swap. 이것이 멤버 함수가 유용하지 않은 이유입니다. 일반적으로 함수를 바꾸는 방법이 아니고의 동작과 관련이 없습니다 std::swap.

그렇다면 std::swap우리가 일을하려면 std::vector<>전문화를 제공해야하고, 제공해야했을 std::swap까요?

namespace std
{
    template <> // important! specialization in std is OK, overloading is UB
    void swap(myclass&, myclass&)
    {
        // swap
    }
}

이 경우에는 분명히 효과가 있지만 눈에 띄는 문제가 있습니다. 함수 전문화는 부분적 일 수 없습니다. 즉, 우리는 이것으로 템플릿 클래스를 특수화 할 수 없으며 특정 인스턴스화 만 가능합니다.

namespace std
{
    template <typename T>
    void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
    {
        // swap
    }
}

이 방법은 일부 시간에는 작동하지만 항상 작동하지는 않습니다. 더 좋은 방법이 있어야합니다.


있습니다! friend함수 를 사용하고 ADL 을 통해 찾을 수 있습니다 .

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}

무언가를 교환하고 싶을 때 를 연결 std::swap한 다음 자격이없는 전화를 겁니다.

using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first

// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap

friend기능 이란 무엇입니까 ? 이 지역에 혼란이 있습니다.

C ++가 표준화되기 전에 friend함수는 "friend name injection" 이라는 기능을 수행했는데 마치 함수가 마치 네임 스페이스에서 작성된 것처럼 코드가 동작 했습니다 . 예를 들어, 이들은 동일한 사전 표준이었습니다.

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

그러나 ADL 이 발명 되었을 때 이것은 제거되었습니다. 이 friend기능은 ADL 을 통해서만 찾을 수 있습니다. 자유 함수로 원한다면 선언해야합니다 ( 예 : this 참조 ). 그러나 소호! 문제가 발생했습니다.

당신이 방금 사용한다면 , 당신은 명시 적으로 "찾고 , 다른 곳은 없다 "고 말했기 때문에 std::swap(x, y)과부하를 찾지 못할 것입니다 std! 그렇기 때문에 일부 사람들은 ADL을 통해 찾을 수있는 기능 과 명시적인 std::자격 을 처리하기 위해 두 가지 기능을 작성하도록 제안했습니다 .

그러나 우리가 본 것처럼, 이것은 모든 경우에 작동하지 않으며 결국 추악한 혼란으로 끝납니다. 대신, 관용적 스와핑은 다른 길로 나아갔습니다. 클래스를 제공하는 작업 대신 , 위와 같이 std::swapQualified를 사용하지 않도록하는 것은 스왑 퍼의 일 swap입니다. 사람들이 알고있는 한 이것은 잘 작동하는 경향이 있습니다. 그러나 거기에는 문제가있다 : 자격이없는 전화를 사용하는 것은 직관적이지 않다!

이를 쉽게하기 위해 Boost와 같은 일부 라이브러리 는 관련 네임 스페이스 와 함께 boost::swap정규화되지 않은 호출을 수행하는 함수를 제공했습니다 . 이것은 일을 간결하게 만드는 데 도움이되지만 여전히 혼란 스럽습니다.swapstd::swap

C ++ 11에는의 동작에 대한 변경 사항이 없으므로 std::swap나와 다른 사람들이 실수라고 생각했습니다. 이것에 조금 익숙하다면 여기를 읽으십시오 .


한마디로 : 멤버 함수는 잡음이 많고 전문화는 추악하고 불완전하지만 friend함수는 완전하고 작동합니다. 그리고 당신이 교환 할 때, boost::swap또는 관련 이없는 자격 swap을 사용하십시오 std::swap.


† 비공식적으로 이름은 함수 호출 중에 고려 될 경우 연결 됩니다. 자세한 내용은 §3.4.2를 참조하십시오. 이 경우 std::swap일반적으로 고려되지 않습니다. 그러나 이를 찾을 수 있도록 연결할 수 있습니다 (unqualified에서 고려한 과부하 세트에 추가 swap).


10
나는 멤버 함수가 단지 노이즈라는 것에 동의하지 않는다. 멤버 함수는 예를 들어 std::vector<std::string>().swap(someVecWithData);, swap두 인수가 모두 비 const 참조에 의해 전달되므로 자유 함수로 는 불가능합니다 .
ildjarn

3
@ ildjarn : 두 줄로 할 수 있습니다. 멤버 함수가 DRY 원칙을 위반합니다.
GManNickG

4
@GMan : DRY 원칙은 하나가 다른 용어로 구현 된 경우 적용되지 않습니다. 그렇지 않으면 아무도의 구현과 클래스지지 않을 것이다 operator=, operator+그리고 operator+=대칭 존재 것으로 예상하지만, 분명히 관련 클래스에 대한 이러한 연산자 / 접수됩니다. 내 의견 으로는 멤버 swap+ 네임 스페이스 범위 swap에 대해서도 마찬가지입니다 .
ildjarn

3
@GMan 너무 많은 기능을 고려하고 있다고 생각합니다. 거의 알려지지 않았지만 심지어는 자신의 것과 똑같이 잘 받아들이 는 것을 선언 function<void(A*)> f; if(!f) { }하기 때문에 실패 할 수도 있습니다 (아마도 일어날 수는 있음). 경우 치명적일 것 ' "연산자 부울 연산자를'내가 왜 구현해야 '!'? 그게 DRY를 위반!의 저자가 생각 오 나는이 '". 에 구현을 구현 하고 에 대한 생성자를 보유하면 두 후보 모두 사용자 정의 변환이 필요하기 때문에 문제가 발생합니다. Aoperator!ffoperator!function<>operator!AAfunction<...>
Johannes Schaub-litb

1
[멤버] 스왑 함수 작성에 대해 어떻게 생각할 수 있는지 생각해 봅시다. 당연히 우리 수업도 그래야합니까? 글쎄,별로. 표준 라이브러리에는 모든 종류의 불필요한 것들이 있으며 멤버 스왑은 그중 하나입니다. 연결된 GotW는 멤버 스왑 기능을 옹호합니다.
Xeverous December

7

이 코드는 ( 거의 모든 방법으로) 다음과 같습니다.

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

클래스 내에 정의 된 친구 함수는 다음과 같습니다.

  • 둘러싸는 네임 스페이스에 배치
  • 자동적으로 inline
  • 추가 자격없이 클래스의 정적 멤버를 참조 할 수 있음

정확한 규칙은 섹션에 있습니다 [class.friend](C ++ 0x 초안의 단락 6과 7을 인용합니다).

클래스가 로컬이 아닌 클래스 (9.8)이고 함수 이름이 규정되지 않고 함수에 네임 스페이스 범위가있는 경우에만 클래스의 친구 선언에서 함수를 정의 할 수 있습니다.

이러한 함수는 암시 적으로 인라인입니다. 클래스에 정의 된 친구 함수는 해당 클래스가 정의 된 클래스의 (어휘) 범위에 있습니다. 클래스 외부에 정의 된 친구 함수가 아닙니다.


2
실제로, 친구 함수는 표준 C ++에서 묶는 네임 스페이스에 배치되지 않습니다. 예전 행동은 "친구 이름 주입"이라고 불렸지만 ADL로 대체되었고 첫 번째 표준으로 대체되었습니다. 의 상단을 참조하십시오 . (하지만 동작은 매우 비슷합니다.)
GManNickG

1
실제로 동등한 것은 아닙니다. 문제의 코드는 swapADL에만 표시 되도록합니다 . 엔 클로징 네임 스페이스의 멤버이지만 해당 이름은 다른 이름 조회 양식에 표시되지 않습니다. 편집 : 나는 @GMan 빨리 다시 것을 볼 :) @ 벤이 항상있었습니다 그 ISO C ++ :)의 방법
요하네스 SCHAUB - litb

2
@Ben : 아니요, 친구 주입은 표준에 존재하지 않았지만 이전에 널리 사용 되었기 때문에 아이디어 (및 컴파일러 지원)가 계속 진행되었지만 기술적으로는 없었습니다. friend함수는 ADL에 의해서만 발견되며, friend액세스 가 가능한 자유 함수 여야 friend하는 경우 클래스 내에서와 같이 선언하고 클래스 외부에서 일반 자유 함수 선언으로 선언해야합니다. 예 를 들어이 답변 에서 그 필요성을 알 수 있습니다 .
GManNickG

2
@towi : 친구 기능이 네임 스페이스 범위에 있기 때문에 세 가지 질문 모두에 대한 답변이 명확 해져야합니다. (1) 무료 기능이며 친구가 개인 및 보호 된 클래스 멤버에게 액세스 할 수 있습니다. (2) 그것은 전혀 회원이 아니며, 인스턴스도 정적도 아닙니다. (3) ADL은 클래스 내에서 검색하지 않지만 friend 함수에는 네임 스페이스 범위가 있으므로 괜찮습니다.
벤 Voigt

1
@ 벤. 사양에서 함수는 네임 스페이스 멤버이고 "함수에 네임 스페이스 범위가 있습니다"라는 구절은 함수가 네임 스페이스 멤버라고 해석 할 수 있습니다 (이 구문의 컨텍스트에 따라 크게 달라짐). 그리고 ADL에서만 볼 수있는 네임 스페이스에 이름을 추가합니다 (실제로 IIRC의 일부는 이름이 추가되었는지 여부에 대한 사양의 다른 부분과 모순됩니다. 그러나 그에 추가 된 호환되지 않는 선언을 감지하려면 이름을 추가해야합니다. 네임 스페이스, 보이지 않는 이름 추가됩니다 (3.3.1p4의 참고 사항 참조).
Johannes Schaub-litb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.