std::swap()을 오버로드하는 방법
-
08-06-2019 - |
문제
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);
// ...
}
};
다른 팁
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)
{
// ...
}
}
이는 기본 클래스에서 작동하지만 두 개의 파생 개체를 바꾸려고 하면 템플릿 스왑이 정확히 일치하기 때문에 std의 일반 버전을 사용하게 됩니다(그리고 파생 개체의 '기본' 부분만 바꾸는 문제를 피할 수 있습니다) ).
메모:마지막 답변에서 잘못된 부분을 제거하기 위해 이것을 업데이트했습니다.맙소사!(이를 지적해준 puetzk와 j_random_hacker에게 감사드립니다)
일반적으로 std::에 항목을 추가하면 안 되는 것이 맞지만네임스페이스에는 사용자 정의 유형에 대한 템플릿 특수화 추가가 특별히 허용됩니다.함수를 오버로드하는 것은 아닙니다.미묘한 차이입니다 :-)
17.4.3.1/1 C ++ 프로그램은 달리 지정되지 않는 한 네임 스페이스 STD 또는 네임 스페이스 네임 스페이스에 선언 또는 정의를 추가하기 위해 정의되지 않았습니다.프로그램은 모든 표준 라이브러리 템플릿에 대한 템플릿 전문화를 네임 스페이스 STD에 추가 할 수 있습니다.표준 라이브러리의 이러한 전문화 (완전 또는 부분)는 선언이 사용자 정의 외부 연결 이름에 의존하지 않는 한 정의되지 않은 동작을 초래하고 템플릿 전문화가 원래 템플릿의 표준 라이브러리 요구 사항을 충족하지 않는 한.
std::swap의 전문화는 다음과 같습니다:
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
template<> 비트가 없으면 허용되는 특수화가 아니라 정의되지 않은 오버로드가 됩니다.기본 네임스페이스를 변경하는 @Wilka의 제안 접근 방식은 사용자 코드에서 작동할 수 있지만(네임스페이스 없는 버전을 선호하는 Koenig 조회로 인해) 보장되지 않으며 실제로는 그렇지 않습니다(STL 구현은 완전히 -한정된 std::swap).
이있다 comp.lang.c++.moderated의 스레드 와 긴 주제에 대한 토론.하지만 대부분은 부분 전문화에 관한 것입니다(현재는 이를 수행할 수 있는 좋은 방법이 없습니다).