std::swap()
정렬 및 할당 중에 많은 표준 컨테이너 (예 : std::list
및 std::vector
)에서 사용됩니다.
그러나의 표준 구현 swap()
은 매우 일반화되어 사용자 정의 유형에 대해서는 비효율적입니다.
따라서 std::swap()
사용자 정의 유형별 구현 으로 오버로딩 하여 효율성을 얻을 수 있습니다 . 그러나 표준 컨테이너에서 사용되도록 어떻게 구현할 수 있습니까?
std::swap()
정렬 및 할당 중에 많은 표준 컨테이너 (예 : std::list
및 std::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);
// ...
}
};
std::sort
ADL을 사용하여 요소를 교체 하는 A 는 C ++ 03을 준수하지 않지만 C ++ 11을 준수합니다. 또한 클라이언트가 비 관상적인 코드를 사용할 수 있다는 사실에 근거한 대답이 -1 인 이유는 무엇입니까?
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
의 네임 스페이스, (그리고 티치 다른 사람에게) 그냥 해 하나 개의 방법이 기억 그냥 쉽다.
template <>
첫 번째 예제를 넣으면 exp::swap(A, A)
gcc에서 출력 을 얻습니다 . 그렇다면 전문화를 선호하지 않는 이유는 무엇입니까?
using std::swap
때 "절대 헤더 파일에 문을 사용하지 마십시오"규칙의 예외 라고 생각 하십니까? 사실 using std::swap
안에 넣지 <algorithm>
않겠습니까? 나는 그것이 소수의 사람들의 코드를 깨뜨릴 수 있다고 생각합니다. 지원을 중단하고 결국에는 추가 하시겠습니까?
using std::swap
헤더 내의 기능 범위 로 제한하려고합니다 . 네, swap
거의 키워드입니다. 그러나 아니, 그것은 키워드가 아닙니다. 따라서 실제로해야 할 때까지 모든 네임 스페이스로 내 보내지 않는 것이 가장 좋습니다. swap
와 매우 비슷 operator==
합니다. 가장 큰 차이점은 operator==
정규화 된 네임 스페이스 구문 으로 호출 하는 것을 생각조차하지 않는다는 것입니다 (너무보기 흉할 것입니다).
(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 에 주제에 대한 긴 토론 과 함께 스레드 가 있습니다 . 하지만 대부분은 부분 전문화에 관한 것입니다 (현재는 좋은 방법이 없습니다).
::swap
가 추가 오버로드는 더 이상 전문 std::swap
과부하 vector
때문에 통화와 후자는 관련이없이 전문성을 캡처. 나는 그것이 어떻게 실용적인 문제인지 잘 모르겠습니다 (하지만 이것이 좋은 생각이라고 주장하는 것도 아닙니다!).