과부하를 일으키는 일반적인 연산자
과부하 연산자의 대부분의 작업은 보일러 플레이트 코드입니다. 연산자가 단순히 구문 설탕이기 때문에 실제 작업이 일반 기능으로 수행 될 수 있으며 종종 전달됩니다. 그러나이 보일러 플레이트 코드를 올바르게 얻는 것이 중요합니다. 실패하면 운영자 코드가 컴파일되지 않거나 사용자 코드가 컴파일되지 않거나 사용자 코드가 놀랍게 동작합니다.
할당 연산자
과제에 대해 할 말이 많습니다. 그러나 대부분은 이미 GMan의 유명한 Copy-And-Swap FAQ 에서 언급 되었으므로 여기서는 대부분의 참조를 생략하고 참조를위한 완벽한 할당 연산자 만 나열합니다.
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
비트 시프트 연산자 (스트림 I / O에 사용)
비트 시프트 연산자 <<
와 >>
는 C에서 상속 한 비트 조작 함수를위한 하드웨어 인터페이스에 여전히 사용되지만 대부분의 응용 프로그램에서 오버로드 된 스트림 입력 및 출력 연산자로 널리 퍼져 있습니다. 비트 조작 연산자로서 오버로드에 대한 지침은 이진 산술 연산자에 대한 아래 섹션을 참조하십시오. 객체가 iostream과 함께 사용될 때 고유 한 사용자 지정 형식 및 구문 분석 논리를 구현하려면 계속하십시오.
가장 일반적으로 오버로드 된 연산자 중 스트림 연산자는 이진 삽입 연산자이며 구문은 멤버인지 멤버가 아닌지에 대한 제한을 지정하지 않습니다. 왼쪽 인수를 변경하기 때문에 (스트림의 상태를 변경 함) 경험 법칙에 따라 왼쪽 피연산자 유형의 멤버로 구현해야합니다. 그러나 왼쪽 피연산자는 표준 라이브러리의 스트림이며 표준 라이브러리에 의해 정의 된 대부분의 스트림 출력 및 입력 연산자는 실제로 자신의 유형에 대한 출력 및 입력 조작을 구현할 때 스트림 클래스의 멤버로 정의됩니다. 표준 라이브러리의 스트림 유형을 변경할 수 없습니다. 따라서 멤버가 아닌 함수로 자신의 유형에 대해 이러한 연산자를 구현해야합니다. 두 가지의 정식 형태는 다음과 같습니다.
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
를 구현할 때 operator>>
스트림 상태를 수동으로 설정하는 것은 읽기 자체가 성공한 경우에만 필요하지만 결과는 예상과 다릅니다.
함수 호출 연산자
함수 함수라고도하는 함수 객체를 만드는 데 사용되는 함수 호출 연산자는 멤버 함수 로 정의되어야 하므로 항상 this
멤버 함수 의 암시 적 인수를 갖습니다 . 이 외에는 0을 포함하여 여러 개의 추가 인수를 사용하도록 오버로드 될 수 있습니다.
구문의 예는 다음과 같습니다.
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
용법:
foo f;
int a = f("hello");
C ++ 표준 라이브러리 전체에서 함수 객체는 항상 복사됩니다. 따라서 자신의 함수 객체는 복사하기에 저렴해야합니다. 함수 객체가 복사하는 데 비용이 많이 드는 데이터를 절대적으로 사용해야하는 경우 해당 데이터를 다른 곳에 저장하고 함수 객체가 참조하는 것이 좋습니다.
비교 연산자
이진 접두사 비교 연산자는 경험 법칙에 따라 비 멤버 함수 1 로 구현해야 합니다. 단항 접두사 부정 !
은 (같은 규칙에 따라) 멤버 함수로 구현되어야합니다. (하지만 일반적으로 과부하는 좋지 않습니다.)
표준 라이브러리의 알고리즘 (예 :) std::sort()
및 유형 (예 :) std::map
은 항상 operator<
존재할 것으로 예상 됩니다. 그러나 해당 유형 의 사용자는 다른 모든 연산자도 존재할 것으로 예상 하므로 정의하는 경우 operator<
연산자 오버로드의 세 번째 기본 규칙을 따르고 다른 모든 부울 비교 연산자도 정의해야합니다. 그것들을 구현하는 정식 방법은 다음과 같습니다.
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
여기서 주목해야 할 중요한 것은이 연산자 중 두 개만 실제로 무언가를 수행하고 다른 작업자는 실제 작업을 수행하기 위해이 두 가지 중 하나에 인수를 전달하는 것입니다.
나머지 이진 부울 연산자 ( ||
, &&
) 를 오버로드하는 구문 은 비교 연산자의 규칙을 따릅니다. 그러나 이러한 2에 대한 합리적인 사용 사례를 찾을 가능성 은 거의 없습니다 .
1 모든 경험 법칙과 마찬가지로 때때로이 규칙을 어기는 이유가있을 수 있습니다. 그렇다면, 이진 비교 구성원에 대한 기능이 될 것입니다 연산자의 왼쪽 피연산자 잊지 마세요 *this
, 필요로 const
너무. 따라서 멤버 함수로 구현 된 비교 연산자에는 다음과 같은 서명이 있어야합니다.
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
( const
끝 부분에 유의하십시오 .)
2 그것은 주목해야한다 내장의 버전 ||
및 &&
사용 바로 가기 의미. 사용자가 정의한 것 (메소드 호출의 구문 설탕이기 때문에)은 바로 가기 시맨틱을 사용하지 않습니다. 사용자는 이러한 연산자에 바로 가기 의미가있을 것으로 예상되며 코드에 따라 달라질 수 있으므로 절대로 정의하지 않는 것이 좋습니다.
산술 연산자
단항 산술 연산자
단항 증가 및 감소 연산자는 접두사와 접미사 형식으로 제공됩니다. 다른 것을 구별하기 위해 접미사 변형은 추가 더미 int 인수를 사용합니다. 증분 또는 감소에 과부하가 걸리면 항상 접두사 및 접미사 버전을 모두 구현해야합니다. 다음은 증가의 표준 구현입니다. 감소는 동일한 규칙을 따릅니다.
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
접미사 변형은 접두사 측면에서 구현됩니다. 또한 postfix는 추가 사본을 수행합니다. 2
단항 빼기 및 더하기 과부하는 흔하지 않으며 피하는 것이 가장 좋습니다. 필요한 경우 멤버 함수로 오버로드되어야합니다.
2 접미사 변형이 더 많은 작업을 수행하므로 접두사 변형보다 사용 효율이 떨어집니다. 이것은 일반적으로 접두사 증가보다 접두사 증가를 선호하는 좋은 이유입니다. 컴파일러는 일반적으로 내장 유형에 대한 추가 접미사 증가 작업을 최적화 할 수 있지만 사용자 정의 유형 (목록 반복자처럼 순진한 것으로 보일 수 있음)에 대해서도 동일한 작업을 수행하지 못할 수 있습니다. 일단 익숙해지면 내장 유형 i++
이 ++i
아닌 경우 i
유형을 변경할 때 기억하기가 매우 어려워 지므로 유형을 변경할 때 코드를 변경해야합니다. 항상 습관을들이는 것이 좋습니다 postfix가 명시 적으로 필요하지 않은 경우 접두사 증가를 사용합니다.
이진 산술 연산자
이진 산술 연산자의 경우 세 번째 기본 규칙 연산자 오버로드를 준수하는 것을 잊지 마십시오. 제공하는 경우 +
, 제공하는 +=
경우, 제공 -
하지 않으면 생략하지 마십시오 -=
. Andrew Koenig는 복합 할당을 가장 먼저 관찰했다고합니다. 연산자는 비 컴파운드의 기반으로 사용할 수 있습니다. 즉, 운전자가되는 +
면에 구현되어 +=
, -
측면에서 구현 -=
등
우리의 경험칙에 따르면 +
, 그 동반자는 비회원이어야하고 +=
, 그들의 왼쪽 주장을 바꾸는 그들의 복합 할당 상대방 ( 등)은 회원이어야합니다. 여기에 대한 예시적인 코드 +=
와 +
; 다른 이진 산술 연산자는 같은 방식으로 구현되어야합니다.
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=
참조 당 결과를 반환하고 결과 operator+
의 복사본 을 반환합니다. 물론 참조를 반환하는 것이 일반적으로 복사본을 반환하는 것보다 더 효율적이지만의 경우 operator+
복사 주위에 방법이 없습니다. 을 쓰면 a + b
결과가 새로운 값이되기를 기대하므로 새로운 값 operator+
을 반환해야합니다. 3
또한 operator+
왼쪽 피연산자 는 const 참조가 아닌 복사로 사용됩니다. 그 이유 operator=
는 사본 당 인수 를 취하는 이유와 동일합니다 .
비트 조작 연산자 ~
&
|
^
<<
>>
는 산술 연산자와 같은 방식으로 구현되어야합니다. 그러나 (과부하 <<
및 >>
출력 및 입력 제외 ) 이들을 과도하게 사용하는 합리적인 사용 사례는 거의 없습니다.
셋 이 즉에서 다시 공과 채취한다 a += b
, 일반적으로,보다 효율적이다 a + b
가능하면 바람직 할 것이다.
배열 첨자
배열 첨자 연산자는 이진 연산자이며 클래스 멤버로 구현해야합니다. 키로 데이터 요소에 액세스 할 수있는 컨테이너와 같은 유형에 사용됩니다. 이를 제공하는 정식 형태는 다음과 같습니다.
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
클래스 사용자가 반환 한 데이터 요소를 변경하지 못하게하려면 operator[]
(이 경우 비 const 변형을 생략 할 수 있음) 항상 연산자의 두 변형을 모두 제공해야합니다.
value_type이 내장 유형을 참조하는 것으로 알려진 경우 연산자의 const 변형은 const 참조 대신 복사본을 반환하는 것이 좋습니다.
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
포인터와 같은 유형의 연산자
자체 반복자 또는 스마트 포인터를 정의하려면 단항 접두사 역 참조 연산자 *
와 이진 접두사 포인터 멤버 액세스 연산자 를 오버로드해야합니다 ->
.
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
이것들도 거의 항상 const와 non-const 버전을 필요로합니다. 들어 ->
오퍼레이터 경우 value_type
이다 class
(또는 struct
또는 union
다른)을 입력 operator->()
until이 재귀 호출 operator->()
이 아닌 클래스 타입의 리턴 값.
단항 주소 연산자에 과부하가 걸리지 않아야합니다.
들어하는 것은 operator->*()
볼 이 질문에 . 거의 사용되지 않으므로 과부하가 걸리지 않습니다. 실제로 반복자조차도 과부하되지 않습니다.
전환 연산자 계속