[c++] std :: swap () 오버로드 방법

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

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

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



답변

스왑을 오버로드하는 올바른 방법은 스왑하는 것과 동일한 네임 스페이스에 작성하여 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);
        // ...
    }
};


답변

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의 네임 스페이스, (그리고 티치 다른 사람에게) 그냥 해 하나 개의 방법이 기억 그냥 쉽다.


답변

(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에게 감사드립니다)


답변

일반적으로 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 에 주제에 대한 토론 과 함께 스레드 가 있습니다 . 하지만 대부분은 부분 전문화에 관한 것입니다 (현재는 좋은 방법이 없습니다).


답변