std :: swap () 오버로드 방법


115

std::swap()정렬 및 할당 중에 많은 표준 컨테이너 (예 : std::liststd::vector)에서 사용됩니다.

그러나의 표준 구현 swap()은 매우 일반화되어 사용자 정의 유형에 대해서는 비효율적입니다.

따라서 std::swap()사용자 정의 유형별 구현 으로 오버로딩 하여 효율성을 얻을 수 있습니다 . 그러나 표준 컨테이너에서 사용되도록 어떻게 구현할 수 있습니까?



답변:


135

스왑을 오버로드하는 올바른 방법은 스왑하는 것과 동일한 네임 스페이스에 작성하여 ADL (인수 종속 조회)을 통해 찾을 수 있도록하는 것 입니다. 특히 쉬운 방법은 다음과 같습니다.

class X
{
    // ...
    friend void swap(X& a, X& b)
    {
        using std::swap; // bring in swap for built-in types

        swap(a.base1, b.base1);
        swap(a.base2, b.base2);
        // ...
        swap(a.member1, b.member1);
        swap(a.member2, b.member2);
        // ...
    }
};

11
C ++ 2003에서는 기껏해야 과소 지정됩니다. 대부분의 구현은 ADL을 사용하여 스왑을 찾지 만 필수 사항은 아니므로 신뢰할 수 없습니다. OP에 표시된대로 특정 콘크리트 유형에 대해 std :: swap을 특수화 있습니다 . 예를 들어 해당 유형의 파생 클래스에 대해 특수화가 사용되기를 기대하지 마십시오.
Dave Abrahams

15
구현에서 여전히 ADL을 사용하여 올바른 스왑을 찾지 못한다는 사실에 놀랄 것 입니다. 이것은 위원회 의 오래된 문제입니다. 구현에서 ADL을 사용하여 스왑을 찾지 않는 경우 버그 보고서를 제출하세요.
Howard Hinnant

3
@Sascha : 첫째, 네임 스페이스 범위에서 함수를 정의하고 있습니다. 이것이 제네릭 코드에 중요한 유일한 정의이기 때문입니다. int et. al. 구성원 함수가 없거나 가질 수 없습니다. std :: sort et. al. 무료 기능을 사용해야합니다. 그들은 프로토콜을 설정합니다. 둘째, 왜 두 가지 구현에 반대하는지 모르겠지만 멤버가 아닌 스왑을 받아들이지 않으면 대부분의 클래스가 비효율적으로 정렬 될 운명입니다. 오버로딩 규칙은 두 선언이 모두 보이면 자격없이 swap이 호출 될 때 더 구체적인 선언 (이 선언)이 선택되도록합니다.
Dave Abrahams 2011

5
@ Mozza314 : 상황에 따라 다릅니다. std::sortADL을 사용하여 요소를 교체 하는 A 는 C ++ 03을 준수하지 않지만 C ++ 11을 준수합니다. 또한 클라이언트가 비 관상적인 코드를 사용할 수 있다는 사실에 근거한 대답이 -1 인 이유는 무엇입니까?
JoeG 2011

4
@curiousguy : 표준을 읽는 것이 표준을 읽는 단순한 문제라면 당신이 옳을 것입니다 :-). 불행히도 저자의 의도가 중요합니다. 따라서 원래 의도가 ADL을 사용할 수 있거나 사용해야한다는 것이었기 때문에 충분히 지정되지 않았습니다. 그렇지 않다면 그것은 C ++ 0x에 대한 평범한 오래된 주요 변경 사항 일뿐입니다. 이것이 제가 "기껏해야"라고 명시하지 않은 이유입니다.
Dave Abrahams 2013

70

Mozza314주의

다음은 일반 std::algorithm호출 의 효과에 대한 시뮬레이션이며 std::swap사용자가 std 네임 스페이스에 스왑을 제공하도록합니다. 이 실험은,이 시뮬레이션은 사용하는 namespace exp대신 namespace std.

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            exp::swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

namespace exp
{
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

나를 위해 이것은 다음과 같이 인쇄됩니다.

generic exp::swap

컴파일러가 다른 것을 출력하면 템플릿에 대한 "2 단계 조회"를 올바르게 구현하지 않은 것입니다.

컴파일러가 C ++ 98 / 03 / 11 중 하나를 준수하면 내가 보여준 것과 동일한 출력을 제공합니다. 그리고 그 경우에 당신이 두려워하는 일이 정확히 일어날 것입니다. 그리고 swap네임 스페이스 std( exp)에 넣는다 고해서 그 일이 멈추지 않았습니다.

Dave와 나는 모두위원회 위원이며 10 년 동안 표준의이 영역을 작업 해 왔습니다 (항상 서로 동의하지는 않음). 그러나이 문제는 오랫동안 해결되어 왔으며 우리 둘 다 해결 방법에 동의합니다. 이 분야에 대한 Dave의 전문가 의견 / 답변은 자신의 위험에 무시하십시오.

이 문제는 C ++ 98이 게시 된 후 밝혀졌습니다. 2001 년경부터 Dave와 저는 이 분야 에서 하기 시작했습니다 . 그리고 이것은 현대적인 솔루션입니다.

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

void swap(A&, A&)
{
    printf("swap(A, A)\n");
}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

출력은 다음과 같습니다.

swap(A, A)

최신 정보

다음과 같은 관찰이 이루어졌습니다.

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

공장! 그럼 왜 사용하지 않습니까?

귀하 A가 클래스 템플릿 인 경우를 고려하십시오 .

// simulate user code which includes <algorithm>

template <class T>
struct A
{
};

namespace exp
{

    template <class T>
    void swap(A<T>&, A<T>&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A<int> a[2];
    exp::algorithm(a, a+2);
}

이제 다시 작동하지 않습니다. :-(

따라서 swap네임 스페이스 std에 넣고 작동하게 할 수 있습니다. 하지만 당신은 넣어 기억해야합니다 swap에서 A템플릿을 때 경우에의 네임 스페이스 : A<T>. 당신이 넣어 두 경우 모두 작동하기 때문에 그리고 swap에서 A의 네임 스페이스, (그리고 티치 다른 사람에게) 그냥 해 하나 개의 방법이 기억 그냥 쉽다.


4
자세한 답변을 주셔서 대단히 감사합니다. 나는 이것에 대해 확실히 덜 알고 있으며 실제로 오버로딩과 전문화가 어떻게 다른 행동을 생성 할 수 있는지 궁금합니다. 그러나 나는 과부하가 아니라 전문화를 제안합니다. template <>첫 번째 예제를 넣으면 exp::swap(A, A)gcc에서 출력 을 얻습니다 . 그렇다면 전문화를 선호하지 않는 이유는 무엇입니까?
voltrevo 2011

1
와! 이것은 정말로 깨달음입니다. 당신은 확실히 나를 확신 시켰습니다. 나는 당신이 그것을 피할 이유가 없다면 (컴파일하는 것 외에는) 당신의 제안을 약간 수정하고 Dave Abrahams의 클래스 내 친구 구문을 사용할 것이라고 생각합니다. 갈라져). 또한 이것에 비추어 볼 using std::swap때 "절대 헤더 파일에 문을 사용하지 마십시오"규칙의 예외 라고 생각 하십니까? 사실 using std::swap안에 넣지 <algorithm>않겠습니까? 나는 그것이 소수의 사람들의 코드를 깨뜨릴 수 있다고 생각합니다. 지원을 중단하고 결국에는 추가 하시겠습니까?
voltrevo 2011

3
클래스 내 친구 구문은 괜찮습니다. using std::swap헤더 내의 기능 범위 로 제한하려고합니다 . 네, swap거의 키워드입니다. 그러나 아니, 그것은 키워드가 아닙니다. 따라서 실제로해야 할 때까지 모든 네임 스페이스로 내 보내지 않는 것이 가장 좋습니다. swap와 매우 비슷 operator==합니다. 가장 큰 차이점은 operator==정규화 된 네임 스페이스 구문 으로 호출 하는 것을 생각조차하지 않는다는 것입니다 (너무보기 흉할 것입니다).
Howard Hinnant 2011

15
@NielKirk : 합병증으로보고있는 것은 단순히 너무 많은 오답입니다. Dave Abrahams의 정답은 복잡하지 않습니다. "스왑을 오버로드하는 올바른 방법은 스왑하는 것과 동일한 네임 스페이스에 작성하여 인수 종속 조회 (ADL)를 통해 찾을 수 있도록하는 것입니다."
Howard Hinnant 2013 년

2
@codeshot : 죄송합니다. Herb는 1998 년 부터이 메시지를 전달하려고 노력해 왔습니다. gotw.ca/publications/mill02.htm 그는이 기사에서 스왑에 대해 언급하지 않았습니다. 그러나 이것은 Herb의 Interface Principle의 또 다른 응용입니다.
Howard Hinnant 2015-08-22

53

(C ++ 표준에 따라) std :: swap을 오버로드하는 것은 허용되지 않지만 std 네임 스페이스에 고유 한 유형에 대한 템플릿 전문화를 추가 할 수 있습니다. 예

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

그러면 표준 컨테이너 (및 다른 모든 곳)의 용도가 일반 컨테이너 대신 전문화를 선택합니다.

또한 스왑의 기본 클래스 구현을 제공하는 것은 파생 형식에 충분하지 않습니다. 예를 들면

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

이것은 Base 클래스에서 작동하지만 두 개의 Derived 객체를 스왑하려고하면 템플릿 스왑이 정확히 일치하기 때문에 std의 제네릭 버전을 사용합니다 (그리고 파생 객체의 '기본'부분 만 스왑하는 문제를 방지합니다. ).

참고 : 마지막 답변에서 잘못된 부분을 제거하기 위해 이것을 업데이트했습니다. 오! (지적 해 주신 puetzk 및 j_random_hacker에게 감사드립니다)


1
대부분 좋은 조언이지만, std 네임 스페이스에서 템플릿을 전문화하는 것과 (C ++ 표준에서 허용하는) 오버로딩 (이는 그렇지 않음) 사이에 puetzk가 언급 한 미묘한 차이 때문에 -1을해야합니다.
j_random_hacker

11
스왑을 사용자 정의하는 올바른 방법은 자신의 네임 스페이스에서 수행하는 것이기 때문에 거부되었습니다 (Dave Abrahams가 다른 답변에서 지적했듯이).
Howard Hinnant

2
downvoting에 대한 나의 이유는 같은 하워드과 같다
데이브 아브라함

13
@HowardHinnant, Dave Abrahams : 동의하지 않습니다. 당신의 대안이 "올바른"방법이라고 주장하는 근거는 무엇입니까? 표준에서 인용 한 puetzk는 특별히 허용됩니다. 이 문제에 익숙하지 않지만 Foo를 정의하고 그렇게 바꾸면 내 코드를 사용하는 다른 사람이 swap (대신 std :: swap (a, b)를 사용할 가능성이 높기 때문에 옹호하는 방법이 정말 마음에 들지 않습니다. a, b) Foo에서 비효율적 인 기본 버전을 자동으로 사용합니다.
voltrevo 2011

5
@ Mozza314 : 댓글 영역의 공간 및 서식 제약으로 인해 완전히 답장 할 수 없었습니다. "Attention Mozza314"라는 제목으로 추가 한 답변을 참조하십시오.
Howard Hinnant 2011

29

일반적으로 std :: 네임 스페이스에 항목을 추가해서는 안되는 것이 맞지만 사용자 정의 유형에 대한 템플릿 전문화 추가는 특별히 허용됩니다. 함수 오버로드는 그렇지 않습니다. 이것은 미묘한 차이입니다 :-)

17.4.3.1/1 C ++ 프로그램에서 별도로 지정하지 않는 한 네임 스페이스 std 또는 네임 스페이스 std가있는 네임 스페이스에 선언 또는 정의를 추가하는 것은 정의되지 않았습니다. 프로그램은 표준 라이브러리 템플릿에 대한 템플릿 전문화를 네임 스페이스 std에 추가 할 수 있습니다. 표준 라이브러리의 이러한 전문화 (전체 또는 부분)는 선언이 외부 연결의 사용자 정의 이름에 의존하지 않고 템플릿 전문화가 원본 템플릿에 대한 표준 라이브러리 요구 사항을 충족하지 않는 한 정의되지 않은 동작을 초래합니다.

std :: swap의 전문화는 다음과 같습니다.

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

template <> 비트가 없으면 허용되는 특수화가 아니라 정의되지 않은 오버로드가됩니다. @Wilka의 제안 된 기본 네임 스페이스 변경 방법은 사용자 코드 (네임 스페이스없는 버전을 선호하는 Koenig 조회로 인해)와 함께 작동 할 수 있지만 보장되지는 않으며 실제로 그렇게되어 있지 않습니다 (STL 구현은 완전히 -qualified std :: swap).

comp.lang.c ++. moderated 에 주제에 대한 토론 과 함께 스레드 가 있습니다 . 하지만 대부분은 부분 전문화에 관한 것입니다 (현재는 좋은 방법이 없습니다).


7
이것 (또는 다른 것)에 대해 함수 템플릿 전문화를 사용하는 것이 잘못된 이유 중 하나는 오버로드와 나쁜 방식으로 상호 작용하며 그 중 스왑을위한 것이 많습니다. 예를 들어 std :: vector <mytype> &에 대해 일반 std :: swap을 전문화하는 경우 과부하 해결 중에 전문화가 고려되지 않기 때문에 표준의 벡터 특정 스왑보다 전문화가 선택되지 않습니다.
Dave Abrahams

4
이것은 Meyers가 Effective C ++ 3ed (항목 25, pp 106-112)에서 권장하는 것이기도합니다.
jww

2
@DaveAbrahams : 당신이 (명시 적 템플릿 인수없이) 전문 경우, 부분적인 순서는 전문화로 발생할 수 vector 버전과 사용됩니다 .
데이비스 청어

1
@DavisHerring 실제로는 부분 주문을 할 때 아무 역할도하지 않습니다. 문제는 당신이 그것을 전혀 부를 수 없다는 것이 아닙니다. 이것은 명백히 덜 특정한 스왑 오버로드가있을 때 일어나는 일입니다 : wandbox.org/permlink/nck8BkG0WPlRtavV
데이브 아브라함

2
@DaveAbrahams : 부분적 순서는 명시 적 전문화가 둘 이상 일치 할 때 전문화함수 템플릿을 선택하는 것입니다. ::swap가 추가 오버로드는 더 이상 전문 std::swap과부하 vector때문에 통화와 후자는 관련이없이 전문성을 캡처. 나는 그것이 어떻게 실용적인 문제인지 잘 모르겠습니다 (하지만 이것이 좋은 생각이라고 주장하는 것도 아닙니다!).
Davis Herring
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.